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,方便点
环境: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对象,这里是不会回结果的,而且对返回结果进行了指定。。。。
既然说了能注入,那就肯定能够注入,于是考虑使用布尔注入:注入如下
预编译的
能够防止SQL注入;
这里在python中试了试,貌似不能够用%df
;500了
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吃到后面的字符,需要看编码规范
例如:要吃掉第二字节
,需要用第一字节
范围中的给出的才能吃掉
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的)
但就查看的日志而言,貌似%df吃不了。。。(要是师傅们测试能过,能给俺教学一手吗?)
无法预编译的点
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; # 关闭链接
?>
结果:
这里指的无法预编译,就直接连'%username%'
都没变。
(麻了,我突然发现,为什么我昨天测试python的时候不直接开日志呢。。。。找了半天在命令行输出语句。。。。)
当然,改一下语句,用concat
拼接能够执行预编译
不能加引号的地方无法预编译
在执行结果日志中看到,执行预编译的地方都会被加双引号
预编译步骤:
1. 执行 addslashes($data),为特殊字符转义
2. 强制加上单引号进行拼接,select * from users where username = '${data}'
由于会强制加上双引号,但是在执行原生SQL的时候,有的地方加不得,例如表名、列名、limit⼦句、order by[desc/asc
];
例如给表名
加上单引号,会被当做字符串进行输出
例如给列名
加上单引号,会报错
所以当程序在特殊位置不能加预编译,就可以考虑一手注入
虽然可以人为加过滤,但是好歹绕了预编译;
标签:02,username,编译,报错,SQL,query,注入 From: https://www.cnblogs.com/upstream-yu/p/16896672.html