文章目录
- 概述注入攻击类型及危害
- 注入攻击的工作原理
- SQL注入
- 代码注入
- XSS(跨站脚本)
- SQL注入
- SQ L注入攻击的本质
- 常见误区及注入点
- 误区1:SQL注入仅发生在GET请求中
- 误区2:没有返回数据的接口不易受注入影响
- 误区3:SQL注入的危害仅限于突破登录
- SQL注入演示及原理
- 防御措施 (jdbcTemplate 、 mybatis)
- 代码注入
- 示例分析
- 解决方案
- 方案一:参数绑定(避免直接拼接)
- 方案二:使用沙箱环境限制权限
- XSS 攻击
- 防御措施与最佳实践
- 防御SQL注入
- 防御代码注入
- 防御XSS攻击
概述注入攻击类型及危害
注入攻击是指攻击者通过传递恶意数据,将这些数据当作代码在目标系统中执行。这类攻击的本质是数据与代码的边界被打破,导致数据被误执行。常见的注入攻击类型包括:
- SQL注入(SQL Injection):攻击者通过恶意构造的SQL语句,破坏数据库的正常查询流程,可能获取、修改甚至删除数据库中的敏感数据。
- 代码注入(Code Injection):外部传入的数据被当作代码执行,导致系统被执行未经授权的指令。
- 跨站脚本攻击(XSS):攻击者通过注入恶意脚本,使得这些脚本在其他用户的浏览器中执行,从而窃取用户数据或劫持用户会话。
注入攻击的工作原理
SQL注入
- 原理:攻击者通过输入特殊构造的SQL语句,使其成为数据库查询的一部分,从而控制数据库执行非预期的操作。例如:通过修改查询条件的值来执行恶意SQL命令。
- 危害:可能导致敏感数据泄露、篡改数据、数据库结构破坏等。
示例:一个简单的SQL查询
SELECT * FROM users WHERE username = 'user' AND password = 'pass';
当攻击者输入' OR '1'='1
作为密码时,SQL变为:
SELECT * FROM users WHERE username = 'user' AND password = '' OR '1'='1';
这导致查询总是返回真,从而绕过认证。
代码注入
- 原理:动态语言环境下,如果将传入的数据误当成代码来执行,黑客就可以传递恶意指令。例如,某些脚本引擎会执行用户传递的计算表达式,若没有严格隔离,可能被恶意利用。
- 危害:导致服务器执行未经授权的代码,可能控制服务器甚至访问内部数据。
示例:
# Python代码注入示例
user_input = "os.system('rm -rf /')"
exec(user_input) # 直接执行了恶意代码
XSS(跨站脚本)
- 原理:黑客将恶意脚本嵌入到用户可以输入的字段中(如评论、用户名),这些脚本在被展示时直接在浏览器中执行,从而窃取用户信息或篡改页面内容。
- 危害:可能导致用户信息泄露、账户劫持、网络钓鱼甚至蠕虫传播。
示例:
<!-- 用户输入恶意脚本 -->
<script>alert('Hacked!');</script>
当其他用户访问该页面时,脚本会在他们的浏览器中执行。
SQL注入
SQ L注入攻击的本质
SQL注入攻击利用了应用程序在处理用户输入时的缺陷,将恶意的SQL代码注入到查询中执行。这种攻击不仅限于登录绕过,还包括数据泄露、数据破坏,甚至是服务器控制等恶意行为。
常见误区及注入点
误区1:SQL注入仅发生在GET请求中
SQL注入不仅限于URL参数(GET请求),Post请求体、Cookies、HTTP Headers等任何输入点都可能成为注入攻击的目标。攻击者常用自动化工具而非手动修改URL来执行注入。
误区2:没有返回数据的接口不易受注入影响
即便接口没有返回任何数据,攻击者仍可利用SQL错误信息推测数据库结构。即使没有明显错误,盲注(如布尔盲注、时间盲注)也能逐步获取数据。
误区3:SQL注入的危害仅限于突破登录
SQL注入可用于获取整个数据库数据,修改或删除表结构,甚至执行操作系统命令。其危害远超登录绕过,必须全方位防御。
SQL注入演示及原理
示例:以下代码展示了一个使用JdbcTemplate的接口,通过SQL字符串拼接来进行模糊查询。该接口存在严重的SQL注入风险。
@PostMapping("jdbcwrong")public void jdbcwrong(@RequestParam("name") String name) {//curl -X POST http://localhost:45678/sqlinject/jdbcwrong\?name\=test//python sqlmap.py -u http://localhost:45678/sqlinject/jdbcwrong --data name=test --current-db --flush-session//python sqlmap.py -u http://localhost:45678/sqlinject/jdbcwrong --data name=test --tables -D "common_mistakes"//python sqlmap.py -u http://localhost:45678/sqlinject/jdbcwrong --data name=test -D "common_mistakes" -T "userdata" --dumplog.info("{}", jdbcTemplate.queryForList("SELECT id,name FROM userdata WHERE name LIKE '%" + name + "%'"));}
攻击者可以通过sqlmap
工具自动化检测该接口,并可以一步步拖库。
防御措施 (jdbcTemplate 、 mybatis)
正确的防护方法:使用参数化查询,避免拼接SQL字符串,让数据库将输入参数作为数据处理,而非代码。例如:
@PostMapping("jdbcright")public void jdbcright(@RequestParam("name") String name) {//curl -X POST http://localhost:45678/sqlinject/jdbcright\?name\=testlog.info("{}", jdbcTemplate.queryForList("SELECT id,name FROM userdata WHERE name LIKE ?", "%" + name + "%"));}
对于 MyBatis 来说,同样需要使用参数化的方式来写 SQL 语句。在 MyBatis 中,“#{}”是参数化的方式,“${}”只是占位符替换
在MyBatis中,应避免$
,采用#
来参数化,并通过函数如CONCAT
实现复杂查询:
@PostMapping("mybatiswrong")public List mybatiswrong(@RequestParam("name") String name) {//curl -X POST http://localhost:45678/sqlinject/mybatiswrong\?name\=test//python sqlmap.py -u http://localhost:45678/sqlinject/mybatiswrong --data name=test --current-db --flush-sessionreturn userDataMapper.findByNameWrong(name);}@PostMapping("mybatisright")public List mybatisright(@RequestParam("name") String name) {//curl -X POST http://localhost:45678/sqlinject/mybatisright?name\=testreturn userDataMapper.findByNameRight(name);}@PostMapping("mybatiswrong2")public List mybatiswrong2(@RequestParam("names") String names) {//curl -X POST http://localhost:45678/sqlinject/mybatiswrong2\?names\=\'test1\',\'test2\'//python sqlmap.py -u http://localhost:45678/sqlinject/mybatiswrong2 --data names="'test1','test2'"return userDataMapper.findByNamesWrong(names);}@Mapperpublic interface UserDataMapper {@Select("SELECT id,name FROM `userdata` WHERE name LIKE '%${name}%'")List<UserData> findByNameWrong(@Param("name") String name);List<UserData> findByNamesWrong(@Param("names") String names);@Select("SELECT id,name FROM `userdata` WHERE name LIKE CONCAT('%',#{name},'%')")List<UserData> findByNameRight(@Param("name") String name);List<UserData> findByNamesRight(@Param("names") List<String> names);}
使用“#{}”来参数化name 参数,对于 LIKE 操作可以使用 CONCAT 函数来拼接 % 符号
对于IN
语句,使用MyBatis的foreach
标签,确保每个元素被正确参数化:
错误示例:
@PostMapping("mybatiswrong2")public List mybatiswrong2(@RequestParam("names") String names) {//curl -X POST http://localhost:45678/sqlinject/mybatiswrong2\?names\=\'test1\',\'test2\'//python sqlmap.py -u http://localhost:45678/sqlinject/mybatiswrong2 --data names="'test1','test2'"return userDataMapper.findByNamesWrong(names);}
<select id="findByNamesWrong" resultType="javaprogramming.commonmistakes.dataandcode.sqlinject.UserData">SELECT id, nameFROM `userdata`WHERE name in (${names})</select>
正确示例: 给 MyBatis 传入一个 List,然后使用其 foreach 标签来拼接出 IN 中的内容,并确保 IN 中的每一项都是使用“#{}”来注入参数
@PostMapping("mybatisright2")public List mybatisright2(@RequestParam("names") List<String> names) {//curl -X POST http://localhost:45678/sqlinject/mybatisright2\?names\=test1,test2return userDataMapper.findByNamesRight(names);}
<select id="findByNamesRight" resultType="javaprogramming.commonmistakes.dataandcode.sqlinject.UserData">SELECT id,name FROM `userdata` WHERE name in<foreach collection="names" item="item" open="(" separator="," close=")">#{item}</foreach></select>
代码注入
在 Web 应用中,动态执行代码是实现复杂逻辑的一种方法,比如通过脚本语言来进行数据验证或规则处理。然而,这种方式也容易导致代码注入漏洞。代码注入漏洞是指外部用户通过特定输入,操纵应用程序的代码执行逻辑,执行恶意代码,甚至危及系统的安全性。
示例分析
以下是一个 Java 代码示例,利用 JavaScript 脚本引擎 ScriptEngine
来动态执行 JavaScript 代码:
private ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
//获得JavaScript脚本引擎
private ScriptEngine jsEngine = scriptEngineManager.getEngineByName("js");@GetMapping("wrong")
public Object wrong(@RequestParam("name") String name) {try {// 通过eval动态执行JavaScript脚本,这里name参数通过字符串拼接方式混入JavaScript代码return jsEngine.eval(String.format("var name='%s'; name=='admin'?1:0;", name));} catch (ScriptException e) {e.printStackTrace();}return null;
}
此代码通过 eval
方法执行了拼接的 JavaScript 代码。传入的 name
参数可以被恶意操纵,例如传入字符串 haha';java.lang.System.exit(0);'
,这会导致恶意的 Java 代码被执行,如关闭整个应用程序。
解决方案
方案一:参数绑定(避免直接拼接)
解决思路类似 SQL 注入的防御方法,将外部输入当作数据而非代码。使用 SimpleBindings
来安全地传递参数,而不是通过字符串拼接执行代码:
@GetMapping("right")
public Object right(@RequestParam("name") String name) {try {// 使用参数绑定,避免直接拼接代码Map<String, Object> parm = new HashMap<>();parm.put("name", name);return jsEngine.eval("name=='admin'?1:0;", new SimpleBindings(parm));} catch (ScriptException e) {e.printStackTrace();}return null;
}
这种方式能够有效阻止恶意代码被执行,因为用户输入不会直接作为代码执行。
方案二:使用沙箱环境限制权限
通过构建一个沙箱环境来限制代码执行的权限,可以防止恶意代码获取敏感资源或执行系统级操作。这里使用 SecurityManager
和 AccessControlContext
创建沙箱环境:
@Slf4j
public class ScriptingSandbox {private ScriptEngine scriptEngine;private AccessControlContext accessControlContext;private SecurityManager securityManager;private static ThreadLocal<Boolean> needCheck = ThreadLocal.withInitial(() -> false);public ScriptingSandbox(ScriptEngine scriptEngine) throws InstantiationException {this.scriptEngine = scriptEngine;securityManager = new SecurityManager() {@Overridepublic void checkPermission(Permission perm) {if (needCheck.get() && accessControlContext != null) {super.checkPermission(perm, accessControlContext);}}};setPermissions(Arrays.asList(new RuntimePermission("getProtectionDomain"),new PropertyPermission("jdk.internal.lambda.dumpProxyClasses", "read"),new RuntimePermission("createClassLoader"),new RuntimePermission("accessDeclaredMembers"),new ReflectPermission("suppressAccessChecks")));}public void setPermissions(List<Permission> permissionCollection) {Permissions perms = new Permissions();if (permissionCollection != null) {for (Permission p : permissionCollection) {perms.add(p);}}ProtectionDomain domain = new ProtectionDomain(new CodeSource(null, (CodeSigner[]) null), perms);accessControlContext = new AccessControlContext(new ProtectionDomain[]{domain});}public Object eval(final String code) {SecurityManager oldSecurityManager = System.getSecurityManager();System.setSecurityManager(securityManager);needCheck.set(true);try {return AccessController.doPrivileged((PrivilegedAction<Object>) () -> {try {return scriptEngine.eval(code);} catch (ScriptException e) {e.printStackTrace();}return null;}, accessControlContext);} catch (Exception ex) {log.error("抱歉,无法执行脚本 {}", code, ex);} finally {needCheck.set(false);System.setSecurityManager(oldSecurityManager);}return null;}
}
使用示例:
@GetMapping("right2")
public Object right2(@RequestParam("name") String name) throws InstantiationException {// 使用沙箱执行脚本ScriptingSandbox scriptingSandbox = new ScriptingSandbox(jsEngine);return scriptingSandbox.eval(String.format("var name='%s'; name=='admin'?1:0;", name));
}
在这个沙箱环境中,即使传入恶意脚本如 haha';java.lang.System.exit(0);'
,也无法获得执行权限,会抛出 AccessControlException
异常。
在实际应用中,建议结合使用参数绑定与沙箱环境两种方式来最大程度地确保动态代码执行的安全性。以下是总结建议:
- 参数绑定:通过
SimpleBindings
方式绑定传入参数,避免代码与数据混合执行。 - 沙箱环境:使用
SecurityManager
限制脚本的权限,防止未经授权的代码执行。
XSS 攻击
防御措施与最佳实践
防御SQL注入
- 使用参数化查询或ORM:避免直接将用户输入嵌入到SQL语句中,使用参数化可以将用户输入视为数据而非代码。
- 输入验证和清理:确保输入的数据符合预期格式。
- 限制数据库权限:使用最低权限原则,防止应用程序账户有过多的权限。
防御代码注入
- 严格验证输入:避免直接执行用户输入的内容,尤其是eval、exec等动态执行语法。
- 使用安全沙箱环境:在受限的环境中执行代码,避免对主系统的影响。
防御XSS攻击
- 对用户输入进行转义:将用户输入的特殊字符转换为无害的字符,防止执行。
- 内容安全策略(CSP):通过HTTP头部的CSP指令限制脚本来源。
- 输入验证和输出编码:对所有的输入进行验证,对输出到HTML的内容进行严格编码。