首页 > 数据库 >[代码审计基础 02]-SQL注入和预编译和预编译绕过

[代码审计基础 02]-SQL注入和预编译和预编译绕过

时间:2022-11-16 17:34:32浏览次数:73  
标签:02 username 编译 报错 SQL query 注入

SQL注入

thinkphp基本没得SQL注入,除非魔改

ORM框架的错误使用

一个专门用来防御SQL注入的框架

错误写法-java/mybatis

<select id = "findUserByname" parameterType="java.lang.String" resultType="cn.itcast.mybatis.po.User">
    <!--拼接mysql,引起SQL注入-->
	SELECT * FROM table WHERE username = '${username}'
</select>
    
    
@Test
public void testFindUserByName() throws Exception{
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //创建UserMapper代理对象
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    //调用userMapper的方法
    list<User> list = userMapper.findUserByName("fuckdada' and 1=1#");
    sqlSession.close();
    System.out.println(list);
}

分析:

SELECT * FROM table WHERE username = '${username}'
如果使用'#{value}'绑定对象,没有漏洞,#{value}会采用预编译的形式针对value进行绑定;会被替换成一个问号(?),并且有参数映射,在调用的时候,会明确?是一个变量,会返回java中找变量的输入("fuckdada' and 1=1#"),这就是预编译。
能够有效防止SQL

如果用${value},这就是一个拼接了。就能够试试注入

错误写法-python/flask/sqlalchemy模块

flask快速入门唯一一点小问题就是,最好用mysql,方便点

mysql增删改查

预编译快速入门

环境:python3+mysql(sqli-labs靶场的security数据库)

@app.route("/",methods=["GET"])
def test():
    username = request.args.get("username")
    res = db.session.query(table).filter("username='{}'".format(username))

分析:

db.session(table).fileter("username={}".format(username))
filter是支持预编译的,但是如果采用format进行填充数据,将会形成sql注入,
可以理解为:采用format直接填充数据之后就成了拼接

为什么说没有经过预编译呢?
因为,在执行中,先"username={}".format(username)进行了拼接,在给orm进行预编译,注入发生在编译之前

正确写法:

@app.route("/",methods=["GET"])
def test():
    username = request.args.get("username")
    res = db.session.query(table).filter(table.username == username))

然后花了一亿点时间写了个代码(我是真的没想到需要用单音号进行一个闭合呀)

sql_injection.py

from flask import Flask,request
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine,text
from models import Users

app = Flask(__name__)
engine = create_engine("mysql+pymysql://root:[email protected]:3306/security", max_overflow=0, pool_size=5,echo = True)
Connection = sessionmaker(bind=engine)
con = Connection()

# ############# 执行ORM操作:查看  #############
@app.route("/inject",methods = ["POST"])
def inject():
    # username = request.form.get("username")
    username = request.form.get("username")
    print("[*] input username:  " + username)
    # users_query = con.query(Users).filter(Users.username==username)  # safe
    # users_query = con.query(Users).filter(Users.username == "{}".format(username))   #safe
    users_query = con.query(Users).filter(text("Users.username='{}'".format(username)))
    users =  users_query.all() #报错注入

    if users:
        for item in users:
            print(item.username)
        return users[0].password
    return "not found"

if __name__ == "__main__":
    app.run()

modles.py(和上一个代码同级就行,虽然这里累赘了,就这吧累了)

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column,Integer,String,Text,ForeignKey,DateTime,UniqueConstraint,Index

Base = declarative_base()

class Users(Base):
    __tablename__ = 'users'#database name
    id = Column(Integer,primary_key=True)
    username = Column(String(32),index=True,nullable=False) # name colume,Index,not null
    password = Column(String(32),nullable = False)
    
    def set_data(self,username="",password=""):
        self.username=username
        self.password=password

    # def set_data(self,username="",password=""):
    #     self.id=id
    #     self.username=username
    #     self.password=password



def init_db():
    """
    根据类创建数据表中
    return
    """
    engine = create_engine(
        "mysql+pymysql://root:[email protected]:3306/security?charset=utf8",
        max_overflow = 0, # 超过连接池大小外最多创建的连接
        pool_size=5 ,# 连接池大小
        pool_timeout = 30,  # 池中没有线程最多等待的时间,否则报错
        pool_recycle=-1,  # 多久之后对线程池中的线程进行一次连接的回收(重置)\
    )
    Base.metadata.create_all(engine)

def drop_db():
    """
    根据类删除数据库表
    :return:
    """
    engine = create_engine(
        "mysql+pymysql://root:[email protected]:3306/security?charset=utf8",
        max_overflow=0,  # 超过连接池大小外最多创建的连接
        pool_size=5,  # 连接池大小
        pool_timeout=30,  # 池中没有线程最多等待的时间,否则报错
        pool_recycle=-1  # 多久之后对线程池中的线程进行一次连接的回收(重置)
    )

    Base.metadata.drop_all(engine)

if __name__ == '__main__':
    # drop_db()
    init_db()

接下来调试代码

有注入的

这里是username='-secure'#',这里是进行的拼接;不过当前的代码来看要是查不出user对象,这里是不会回结果的,而且对返回结果进行了指定。。。。

image-20221115164913667

既然说了能注入,那就肯定能够注入,于是考虑使用布尔注入:注入如下

image-20221115170058428

预编译的

SQL注入&预编译-奇安信攻防社区(写得真的好)

image-20221115170253283

能够防止SQL注入;

这里在python中试了试,貌似不能够用%df;500了

image-20221115171536761

SQL注入是在注入什么?

注入类型差异

核心思维:

SQL注入,就是执行一段SQL语句,关键在于SQL数据库类型

编程语言:

不同编程语言最终的目的都是为了将payload送入数据库层进行执行,能够看到注入点即可,语言不重要

产生注入的输入点

输入点决定了用什么样的Vector,以及是否需要绕过

SQL注入结构

一般来说,一个普通的SQL注入结构如下

action:select
object:table
subject(target):*
condition:
	key:usersname
	value:$username// user input

如果能够执行执行堆叠注入(是否能够加分号;),那么能够跳出action:select,执行更多的操作如,action:delete action:update

爆破数据的速度

报错 = 联合注入>外带数据>布尔盲注>时间盲注

为什么把报错放在第一位?

实际环境中,联合注入不一定能够构造出来,报错注入范围更加广阔,某些情况下,报错比联合快

输入点很多

不同的输入点有不同注入方式,代表着能不能绕过框架什么的,例如在预编译中,order之后能绕鱼变意思

可能进行SQL注入

宽字节注⼊

们知道字节是计算机存储世界中最⼩的衡量单位,1Byte = 8bits。所以⼀个字节最⼤能够表
示2^8=256个字符。
所以:
 对ascii编码⽽⾔,⼀个字符⽤⼀个字节就可以表示,所以ascii编码最多可以表示256个字
符。
 对GBK编码⽽⾔,⼀个汉字字符需要⽤两个字节表示。所以gbk编码理论上最多可以表示
256*256个字符。

案例:看SQLlabs的宽字节就行

<?php
$db = init_db();
$db->query("set SET NAMES 'gbk'); //设置gbk字符集
$username = addslashes($_GET['username']); //input: fuckdada' and 1=1#
$db->query("select * from table where username = '$username'"); 
?>

编码规范

要用%df吃到后面的字符,需要看编码规范

例如:要吃掉第二字节,需要用第一字节范围中的给出的才能吃掉

image-20221116110742268

Hsql

Hibernate是⼀种ORM框架,⽤来映射与tables相关的类定义(代码),并包含⼀些⾼级特 性,包括缓存以及继承,通常在Java与.NET中使⽤(可参考NHibernate),但在Java⽣态系 统中更受欢迎。近来似乎逐渐有被Mybatis取代的趋势。

与Mybatis不同的是,Hibernate虽然也⽤了xml作为映射模型。但是他构成了⼀套⾃⼰的解析 引擎和语法,也就是HQL

https://www.cnblogs.com/chenssy/archive/2012/07/17/2594919.html 
https://blog.51cto.com/jiaojusuimu/1881287

用户的输⼊作为HQL的⼀部分,先经过Hibernate解析引擎,渲染成sql语句,然后再进⼊到 数据库层进⾏查询。

所以不仅需要满足HQL的规范,同样需要满足渲染之后SQL的语法规范;

目前一般不考虑sql能够完全执⾏成功,⽽是利⽤sql报错注⼊+框架开启报错(因为java很多框架中都开启了报错),将有⽤的数据直接在错误回显中爆出来;

https://www.trustwave.com/en-us/resources/blogs/spiderlabs-blog/hqlinjection-exploitation-in-mysql/
https://blog.sonarsource.com/exploiting-hibernate-injections/
https://www.sec-in.com/article/144
https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20In
jection/HQL%20Injection.md
https://conference.hitb.org/hitbsecconf2016ams/materials/D2T2%20-
%20Mikhail%20Egorov%20and%20Sergey%20Soldatov%20-
%20New%20Methods%20for%20Exploiting%20ORM%20Injections%20in%20Java%20App
lications.pdf

预编译之下的注入(复现失败)

php预编译——宽字节绕过

<?php
$username = $_GET['username'];
$db = "mysql:host=127.0.0.1;dbname=test;charset=gbk";
$dbname = "root";
$passwd = "root";
$conn = new PDO($dbs, $dbname, $passwd);
$conn->query('SET NAMES GBK');
$stmt = $conn->prepare("select * from table where username =
:username");
$stmt->bindParam(":username",$username);
$stmt->execute();
?

开始日志:(默认没开启,网上找一个一堆开慢查询的....)(51才是nb的)

image-20221116152245851

但就查看的日志而言,貌似%df吃不了。。。(要是师傅们测试能过,能给俺教学一手吗?)

image-20221116162405264

无法预编译的点

like后

这⾥再展开讲讲那些没办法⽤orm(没办法预编译)去保护的输⼊点,并且这种是通例,也就 是⽆视语⾔的。

在sql语句的模糊查找⾥⾯⽤的关键字like,⽽like关键字默认是不会预编译的(如果使⽤ Mybatis则是预编译报错)。数据库⽅给出的原因好像是like预编译会造成慢查询和DOS。

yu6.php
<?php
$username = $_POST['username']; // 接收username
# 建立数据库连接
$dbs = "mysql:host=127.0.0.1;dbname=security";
$dbname = "root";
$passwd = "123456";
// 创建连接,选择数据库,检测连接
try{
$conn = new PDO($dbs, $dbname, $passwd);
echo "Sucussful<br/>";
}
catch (PDOException $e){
die ("Error!: " . $e->getMessage() . "<br/>");
}
# 预编译语句
$conn -> setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
// $stmt = $conn->prepare("select * from users where username like '%:username%'"); #无预编译
$stmt = $conn->prepare("select * from users where username like concat('%',:username,'%')"); #生效
$stmt->bindParam(":username",$username);
$stmt->execute();
$row = $stmt->fetch();
print_r($stmt);
print_r($row);
// print("asdf");
$conn=null; # 关闭链接
?>

结果:

image-20221116163557827

这里指的无法预编译,就直接连'%username%'都没变。

(麻了,我突然发现,为什么我昨天测试python的时候不直接开日志呢。。。。找了半天在命令行输出语句。。。。)

当然,改一下语句,用concat拼接能够执行预编译

image-20221116164559358

不能加引号的地方无法预编译

在执行结果日志中看到,执行预编译的地方都会被加双引号

预编译步骤:
1. 执行 addslashes($data),为特殊字符转义
2. 强制加上单引号进行拼接,select * from users where username = '${data}'

由于会强制加上双引号,但是在执行原生SQL的时候,有的地方加不得,例如表名、列名、limit⼦句、order by[desc/asc];

例如给表名加上单引号,会被当做字符串进行输出

image-20221116165349142

例如给列名加上单引号,会报错

image-20221116165541800

所以当程序在特殊位置不能加预编译,就可以考虑一手注入

image-20221116165716711

虽然可以人为加过滤,但是好歹绕了预编译;

标签:02,username,编译,报错,SQL,query,注入
From: https://www.cnblogs.com/upstream-yu/p/16896672.html

相关文章

  • 2022-01-27 redis集群技术调研
    目录​​摘要:​​​​redis集群方案选型:​​​​redis集群前端代理proxy技术选型:​​​​redis集群的扩容/缩容:​​​​rediscluster集群的节点高可用​​​​redis节......
  • 2021-07-17 从零开始的一日HTML
    <!DOCTYPEhtml><html><head><metacharset="UTF-8"><title>狗子的工作站</title></head><body><divalign="left"><img......
  • [递归专题打卡]2021 6.30-7.2
    2021/6/30链接:​​https://ac.nowcoder.com/acm/problem/14310​​​ ★字符串逆序输入一个字符串,长度在100以内,按相反次序输出其中的所有字符。输入描述:输入一个字符串......
  • DTOJ 2022-11-15 测试 题解
    测试成果100+100+50+10=260还行吧(虽然T2做法很迷惑)A惊鸿(grace)DTOJP6367题面大意给定一个\(n\)行\(m\)列的仅包含小写字母的矩阵\(A\)。求从\((1,1)\)......
  • 群英荟萃,解码科技未来 2022深圳高交会在深圳精彩启幕
    科技改革驱动创新,科技创新驱动发展。11月15日,第二十四届高交会如期开幕。深圳市委副书记、市长覃伟中主持了本届高交会开幕式。广东省委副书记、深圳市委书记孟凡利,商务部......
  • EBS 常用sql
    1)查看请求挂在哪个状态下SELECTfcpv.concurrent_program_nameFROMfnd_request_groupsfrg,--请求组fnd_request_group_unitsfrgu,--请求单元......
  • python 3.6下 安装mysqlclient
    倒腾了大半天,终于把mysqlclient安装成功,赶紧把步骤进行梳理并记录1、python安装好以后,首先要看一下自己的python是32还是64位的,这关系到你下载的mysqlclient。查看方法:......
  • SQL Server 读写分离配置的一些问题
    SQLServer读写分离配置的一些问题 1,新建发布服务器遇到此服务器上未安装复制组件先执行以下sqlusemastergoselect@@servername;selectserverproperty('servern......
  • sql server 数据库like 实现参数化的思路
    1.使用CHARINDEX涵数返回非-1值表是有值,否则没有查询数据CHARINDEX原理:通过能够找到对应的字符串,则返回该字符串位置,否则返回0。基本语法如下:CHARINDEX(expression......
  • CSP 202206-1 归一化处理 C++
    1#include<iostream>2#include<vector>3#include<math.h>45intmain(){6intx{},sum{};7std::cin>>x;8std::vector<int>n(x,0);......