安全相关的php学习

hello world

学任何语言的精髓就是hello world了

这里使用phpstorm作为php编译器

这是php环境的配置

1
2
3
<?php
echo "hello world!"
?>

这就是hello world了

输出成功

这里学习到了php的开头是 <?php 结尾是?>

输出用echo输出

注意echo是输出一个或者多个字符串

print也可以输出,但是它输出的只能是一个字符串,返回值总是为1

一般都用echo

注释

单行注释用//

多行注释用/* */

变量

变量前面加上$

php是弱类型语言,不用在变量前面加类型

弱类型安全

以下表达式都是相等的

  • ‘’==0==false==NULL
  • ‘123’==123
  • ‘abc’==0
  • ‘123a’==123
  • ‘0x01’==1
  • ‘0e123456789’==’0e987654321’
  • [false] ==[0]==[NULL]==[‘’]
  • true == 1

比较相等可以用”==”或者”===”,两个等于号会自动进行类型转换

超级全局常量

$GLOBALS

$GLOBALS就是一个数组,专门用来存变量的,存的变量是全局变量。比如[‘xx’:’qqq’,’yy’:’rr’]这样一个数组的意思就是有两个元素,xx和yy,然后xx元素的值为qqq,yy元素的值为rr

这里顺便介绍一下global,他是声明全局变量的

比如global $a,$b就是声明a和b两个全局变量

下面比较一下global和$GLOBALS

使用global定义

1
2
3
4
5
6
7
8
9
$a = 1;
$b = 2;
function Sum()
{
global $a, $b; //在里面声明为全局变量
$b = $a + $b;
}
Sum();
echo $b;

使用$GLOBALS定义全局变量

1
2
3
4
5
6
7
$a = 1;
$b = 2;
function Sum(){
$GLOBALS["b"] = $GLOBALS["a"] + $GLOBALS["b"];//定义变量时每个都要定义
}
Sum();
echo $b; //输出结果为2

以上两种写法等价

$_SERVER

$_SERVER 是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。

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
$_SERVER['PHP_SELF'] #当前正在执行 脚本的文件名,与 document root相关。
$_SERVER['argv'] #传递给该 脚本的参数。
$_SERVER['argc'] #包含传递给程序的 命令行参数的个数(如果运行在命令行模式)。
$_SERVER['GATEWAY_INTERFACE'] #服务器使用的 CGI 规范的版本。例如,“CGI/1.1”。
$_SERVER['SERVER_NAME'] #当前 运行脚本所在服务器 主机的名称。
$_SERVER['SERVER_SOFTWARE'] #服务器标识的字串,在响应请求时的头部中给出。
$_SERVER['SERVER_PROTOCOL'] #请求页面时通信协议的名称和版本。例如,“HTTP/1.0”。
$_SERVER['REQUEST_METHOD'] #访问页面时的请求方法。例如:“GET”、“HEAD”,“POST”,“PUT”。
$_SERVER['QUERY_STRING'] #查询(query)的字符串。
$_SERVER['DOCUMENT_ROOT'] #当前 运行脚本所在的文档根目录。在服务器配置文件中定义。
$_SERVER['HTTP_ACCEPT'] #当前请求的 Accept: 头部的内容。
$_SERVER['HTTP_ACCEPT_CHARSET'] #当前请求的 Accept-Charset: 头部的内容。例如:“iso-8859-1,*,utf-8”。
$_SERVER['HTTP_ACCEPT_ENCODING'] #当前请求的 Accept-Encoding: 头部的内容。例如:“gzip”。
$_SERVER['HTTP_ACCEPT_LANGUAGE']#当前请求的 Accept-Language: 头部的内容。例如:“en”。
$_SERVER['HTTP_CONNECTION'] #当前请求的 Connection: 头部的内容。例如:“Keep-Alive”。
$_SERVER['HTTP_HOST'] #当前请求的 Host: 头部的内容。
$_SERVER[' HTTP_REFERER'] #链接到当前页面的前一页面的 URL 地址。
$_SERVER[' HTTP_USER_AGENT'] #当前请求的 User-Agent: 头部的内容。
$_SERVER['HTTPS'] #如果通过https访问,则被设为一个非空的值(on),否则返回off
$_SERVER['REMOTE_ADDR'] #正在浏览当前页面用户的 IP 地址。
$_SERVER['REMOTE_HOST'] #正在浏览当前页面用户的 主机名。
$_SERVER['REMOTE_PORT'] #用户连接到服务器时所使用的端口。
$_SERVER['SCRIPT_FILENAME'] #当前执行 脚本的 绝对路径名。
$_SERVER['SERVER_ADMIN'] # 管理员信息
$_SERVER['SERVER_PORT'] #服务器所使用的端口
$_SERVER['SERVER_SIGNATURE'] #包含服务器版本和 虚拟主机名的字符串。
$_SERVER['PATH_TRANSLATED'] #当前 脚本所在文件系统(不是文档根目录)的基本路径。
$_SERVER['SCRIPT_NAME'] #包含当前 脚本的路径。这在页面需要指向自己时非常有用。
$_SERVER['REQUEST_URI'] #访问此页面所需的 URI。例如,“/index.html”

这里可能不全,待补充

$_REQUEST

用于收集HTML表单提交的数据。

$_GET

HTML form标签的属性method=”get”时,$_GET可以收集URL中发送的数据。

$_POST

HTML form标签的属性method=”post”时,$_POST可以收集表单中发送的数据。

$_FILES

$_ENV

$_SESSION

函数

常用

array()

创建数组

bool is_numeric ( mixed $var )

检测变量是否为数字或数字字符串,注意,可以是16进制,字符串转16进制可以绕过。is_numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值。

由于php是弱类型语言,会使用这个函数来判断一下。

rand(min,max)

生成随机整数

sleep(seconds)

延迟执行当前脚本若干秒

bool isset ( mixed $var [, mixed $… ] )

检测变量是否已设置并且非 NULL

die(message)

输出一条消息,并退出当前脚本。

md5()

以下值在md5加密后以0E开头:

QNKCDZO
240610708
s878926199a
s155964671a
s214587387a
可以用于md5相等,但两值不相等的情况

base64_encode()、base64_decode()、sha1() 和 strcmp()

传入数组,返回NULL

字符串相关

正则表达式

  • int preg_match ( string $pattern , string $subject)

执行一个正则表达式匹配

这里需要注意一下 i ,表示大小写不敏感,即php、PHP、PhP都会被过滤掉,有的时候没有写i,黑名单里面有select关键字时,我们可以使用SELECT来绕过。

  • mb_substr ( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] ) : string

返回字符串的一部分,字符串可以包含中文,substr函数是不能包含中文的。

子字符串位置

  • mb_strpos ( string $haystack , string $needle [, int $offset = 0 [, string $encoding = mb_internal_encoding() ]] ) : int

查找字符串在另一个字符串中首次出现的位置(汉字占一个位置),如果没有找到字符串则返回 false。类似的还有:

strpos()-查找字符串在另一字符串中最后一次出现的位置(汉字占两个位置)
strrpos()-查找字符串在另一字符串中最后一次出现的位置(区分大小写)
stripos()-查找字符串在另一字符串中第一次出现的位置(不区分大小写)
strripos()-查找字符串在另一字符串中最后一次出现的位置(不区分大小写)

安全问题

由于返回的是false,一旦使用==进行判断,我们可以构造第二个字符串是第一个的开始,即可绕过

1
2
3
4
5
6
7
8
<?php
$str1 = "abcd";
$str2 = "ab";
if(strpos($str1,$str2)==false)
{
echo "flag";
}
?>

输出为flag

  • str_replace(find,replace,string,count)

替换字符串中的一些字符(区分大小写),注意,可以通过双写或者大小写进行绕过。

  • mixed preg_filter ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

执行一个正则表达式搜索和替换。这个就不太好弄了,可能需要编码绕过。

  • htmlspecialchars(string,flags,character-set,double_encode)

把一些预定义的字符转换为 HTML 实体。

1
2
3
4
5
6
7
预定义的字符是:

& (和号)成为 &amp;
" (双引号)成为 &quot;
' (单引号)成为 '
< (小于)成为 &lt;
> (大于)成为 &gt;

故可以使用单引号的payload进行绕过。

  • strip_tags(string,allow)

剥去字符串中的 HTML、XML 以及 PHP 的标签。

  • addslashes(string)

返回在预定义的字符前添加反斜杠的字符串。

1
2
3
4
5
6
预定义字符是:

单引号(')
双引号("
反斜杠(\)
NULL

数据库相关

mysqli

mysqli_connect(host,username,password,dbname,port,socket)
打开一个到 MySQL 服务器的新的连接。

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

mysqli_close(connection)
关闭先前打开的数据库连接。

mysqli_query(connection,query,resultmode)
执行某个针对数据库的查询

mysqli_fetch_assoc(result)
从结果集中取得一行作为关联数组

mysqli_fetch_row(result)
从结果集中取得一行,并作为枚举数组返回

mysqli_num_rows(result)
返回结果集中行的数量

mysqli_real_escape_string(connection,escapestring)
转义在 SQL 语句中使用的字符串中的特殊字符,难道转义后我们就没有办法注入了?NO!!!

php官方函数参考手册中写到,会被进行转义的字符包括: NUL (ASCII 0),\n,\r,\,’,” 和 Control-Z.

也就是或如果是数字型注入,例如,payload是 1 and 1=1# ,那么这个函数没有用,因为payload中不包含以上字符。

另外,在调用 mysqli_real_escape_string() 函数之前, 必须先通过调用mysqli_set_charset()函数或者在 MySQL 服务器端设置字符集。如果字符集设置不当,可以通过宽字节注入来进行绕过。

pdo

PHP 数据对象 (PDO) 扩展为PHP访问数据库定义了一个轻量级的一致接口。

PDO 提供了一个数据访问抽象层,这意味着,不管使用哪种数据库,都可以用相同的函数(方法)来查询和获取数据。

  • public PDOStatement PDO::prepare ( string $statement [, array $driver_options = array() ] )

为 PDOStatement::execute() 方法准备要执行的SQL语句,SQL语句可以包含零个或多个命名(:name)或问号(?)参数标记,参数在SQL执行时会被替换。这样就没法破坏sql语句的结构,不是拼接,也就是sql注入防御的一种。

举例:

1
prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );

你以为sql语句是

1
SELECT first_name, last_name FROM users WHERE user_id = id LIMIT 1;

于是使用 1 or 1=1#作为payload,认为下面的sql语句会被执行

1
SELECT first_name, last_name FROM users WHERE user_id = 1 or 1=1#

但实际上执行的是

1
SELECT first_name, last_name FROM users WHERE user_id = 1 or 1=1# LIMIT 1;

或者说 直接判断 1 or 1=1# 是不是数字型参数,不是的话根本就不会去执行,也就安全了。

  • bool PDOStatement::bindParam ( mixed $parameter , mixed &$variable [, int $data_type = PDO::PARAM_STR [, int $length [, mixed $driver_options ]]] )

绑定一个PHP变量到用作预处理的SQL语句中的对应命名占位符或问号占位符。

例如(绑定变量id与prepare里的id):

1
bindParam( ':id', $id, PDO::PARAM_INT )
  • bool PDOStatement::execute ([ array $input_parameters ] )

执行预处理过的语句。

1
mixed PDOStatement::fetch ([ int $fetch_style [, int $cursor_orientation = PDO::FETCH_ORI_NEXT [, int $cursor_offset = 0 ]]] )

从一个 PDOStatement 对象相关的结果集中获取下一行。

1
int PDOStatement::rowCount ( void )

返回上一个由对应的 PDOStatement 对象执行DELETE、 INSERT、或 UPDATE 语句受影响的行数。

伪协议相关

函数

https://www.cnblogs.com/-an-/p/12372220.html

伪协议方法

https://www.jianshu.com/p/9a7148a753e0

反序列化漏洞

PHP提供serialize和unserialize函数,将任意类型的数据转换成string类型或者相反。当unserialize函数的参数被用户控制的时候就会形成反序列化漏洞。

serialize函数

serialize() 函数用于序列化对象或数组,并返回一个字符串,序列化对象后,可以很方便的将它传递给其他需要它的地方,且其类型和结构不会改变。

string serialize ( mixed $value )

参数

$value: 要序列化的对象或数组。

返回

字符串

unserialize函数

unserialize() 函数用于将通过serialize() 函数序列化后的对象或数组进行反序列化,并返回原始的对象结构。

mixed unserialize ( string $str )

参数

$str: 序列化后的字符串。

返回

返回的是转换之后的值,可为 integer、float、string、array 或 object。

如果传递的字符串不可解序列化,则返回 FALSE,并产生一个 E_NOTICE。

魔法函数

序列化对象就要谈到类,其中有三个魔法函数:__construct、__destruct、__toString。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php 
class test{
var $url;
var $name;
function __construct( $par1, $par2 ) {
$this->url = $par1;
$this->name = $par2;
echo "call __construct<br>";
}
function __toString() {
echo "call __toString<br>";
return $this->url;

}
function __destruct() {
echo "call __destruct<br>";
echo "bye<br>";
}
}

$t = new test('https://blog.csdn.net/lady_killer9','FrankYu');
echo $t . PHP_EOL;
?>

这里的br是换行符,编译器的原因没有自动换行

  • __construct在创建对象时被调用
  • __toString在对象被当做字符串时被调用
  • __destruct在脚本结束时被调用

举例

有这样一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php 
class test{
var $path = "";
function __toString() {
$file = fopen($this->path, "r") or exit("无法打开文件!");
// 读取文件每一行,直到文件结尾
while(!feof($file))
{
echo fgets($file). "<br>";
}
fclose($file);
return $this->path;
}
}
$para = '';
echo unserialize($para);
?>

其中,para是你可控的,代码的作用就是反序列化你传入的参数,然后打印出来。

分析:打印必调用__toString函数,__toString函数中会打开$path文件,所以,我们需要构造一个path为敏感文件路径的test类对象,并得到序列化后的字符串,将该字符串作为参数para。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php 
class test{
var $path = "";
function __toString() {
$file = fopen($this->path, "r") or exit("无法打开文件!");
// 读取文件每一行,直到文件结尾
while(!feof($file))
{
echo fgets($file). "<br>";
}
fclose($file);
return $this->path;
}
}
$hack = new test();
$hack->path = "/box/script.php";
echo serialize($hack);
?>

得到序列化字符串

成功读取了敏感文件/box/script.php

显示了服务器保存的代码

更多函数,请查看php官方手册

注:本文章大部分摘录自https://blog.csdn.net/lady_killer9/article/details/108978062