//程序启动时进行表结构和数据初始化 @PostConstruct public void init() { //删除表 jdbcTemplate.execute("drop table IF EXISTS `userdata`;"); //创建表,不包含自增ID、用户名、密码三列 jdbcTemplate.execute("create TABLE `userdata` (\n" + " `id` bigint(20) NOT NULL AUTO_INCREMENT,\n" + " `name` varchar(255) NOT NULL,\n" + " `password` varchar(255) NOT NULL,\n" + " PRIMARY KEY (`id`)\n" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); //插入两条测试数据 jdbcTemplate.execute("INSERT INTO `userdata` (name,password) VALUES ('test1','haha1'),('test2','haha2')"); } @Autowired private JdbcTemplate jdbcTemplate;
//用户模糊搜索接口 @PostMapping("jdbcwrong") public void jdbcwrong(@RequestParam("name") String name) { //采用拼接SQL的方式把姓名参数拼到LIKE子句中 log.info("{}", jdbcTemplate.queryForList("SELECT id,name FROM userdata WHERE name LIKE '%" + name + "%'")); }
[13:22:27.375] [http-nio-45678-exec-10] [ERROR] [o.a.c.c.C.[.[.[/].[dispatcherServlet]:175 ] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DuplicateKeyException: StatementCallback; SQL [SELECT id,name FROM userdata WHERE name LIKE '%test'||(SELECT 0x694a6e64 WHERE 3941=3941 AND (SELECT 9927 FROM(SELECT COUNT(*),CONCAT(0x71626a7a71,(SELECT MID((IFNULL(CAST(password AS NCHAR),0x20)),1,54) FROM common_mistakes.userdata ORDER BY id LIMIT 1,1),0x7170706271,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a))||'%']; Duplicate entry 'qbjzqhaha2qppbq1' for key '<group_key>'; nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'qbjzqhaha2qppbq1' for key '<group_key>'] with root cause java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'qbjzqhaha2qppbq1' for key '<group_key>'
@PostMapping("jdbcright") public void jdbcright(@RequestParam("name") String name) { log.info("{}", jdbcTemplate.queryForList("SELECT id,name FROM userdata WHERE name LIKE ?", "%" + name + "%")); }
<select id="findByNamesWrong" resultType="org.geekbang.time.commonmistakes.codeanddata.sqlinject.UserData"> SELECT id,name FROM `userdata` WHERE name in (${names}) </select>
但是,这样直接把外部传入的内容替换到IN内部,同样会有注入漏洞:
1 2 3 4
@PostMapping("mybatiswrong2") public List mybatiswrong2(@RequestParam("names") String names) { return userDataMapper.findByNamesWrong(names); }
@PostMapping("mybatisright2") public List mybatisright2(@RequestParam("names") List<String> names) { return userDataMapper.findByNamesRight(names); }
<select id="findByNamesRight" resultType="org.geekbang.time.commonmistakes.codeanddata.sqlinject.UserData"> SELECT id,name FROM `userdata` WHERE name in <foreach collection="names" item="item" open="(" separator="," close=")"> #{item} </foreach> </select>
public ScriptingSandbox(ScriptEngine scriptEngine) throws InstantiationException { this.scriptEngine = scriptEngine; securityManager = new SecurityManager(){ //仅在需要的时候检查权限 @Override public 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 FilePermission(Shell.class.getProtectionDomain().getPermissions().elements().nextElement().getName(),"read"), new RuntimePermission("createClassLoader"), new RuntimePermission("accessClassInPackage.jdk.internal.org.objectweb.*"), new RuntimePermission("accessClassInPackage.jdk.nashorn.internal.*"), 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}); }
[13:09:36.080] [http-nio-45678-exec-1] [ERROR] [o.g.t.c.c.codeinject.ScriptingSandbox:77 ] - 抱歉,无法执行脚本 var name='haha';java.lang.System.exit(0);''; name=='admin'?1:0; java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "exitVM.0") at java.security.AccessControlContext.checkPermission(AccessControlContext.java:472) at java.lang.SecurityManager.checkPermission(SecurityManager.java:585) at org.geekbang.time.commonmistakes.codeanddata.codeinject.ScriptingSandbox$1.checkPermission(ScriptingSandbox.java:30) at java.lang.SecurityManager.checkExit(SecurityManager.java:761) at java.lang.Runtime.exit(Runtime.java:107)
//注册自定义的Jackson序列器 @Bean public Module xssModule() { SimpleModule module = new SimpleModule(); module.addDeserializer(String.class, new XssJsonDeserializer()); module.addSerializer(String.class, new XssJsonSerializer()); return module; }
public class XssJsonSerializer extends JsonSerializer<String> { @Override public Class<String> handledType() { return String.class; }