当其注入到命令时,命令就会变成:
select * from db_user where user_name ='validuser' OR '1'='1'
。即使所输入字符串不是来源于不可信赖的数据源,程序仍然存在着一定风险。
解决思路:
在能够使用#
的地方,直接使用#
代替$
。如果在不能直接使用#
的场景,例如需要加入字段
本身的地方:
-
模糊查询like
-
修改前:
select * from db_user where user_name like '%${likeColumn}%'
-
修改后:
select * from db_user where user_name like concat('%',#{likeColumn},'%')
从 ${} 改为 #{},从sql拼接到 concat方式
-
范围查询in
-
修改前:
select * from db_user where id in (${id})
-
修改后:
select * from aaa where id in <foreach collection="ids" item="item" open="("separator="," close=")">#{item} </foreach>
用mybatis的for-each标签替代
-
排序order by
-
order by 后面的字段写死在应用程序内,不开放给外部调用
-
order by 后面的字段增加一个范围限制,即设置白名单,每次传参进来时判断该字段是可信的
2、SQL注入
直接的SQL语句拼接可能会导致SQL注入,原因和1一样。
public void doPrivilegedAction(String username, char[] password) throws SQLException {
Connection connection = getConnection();
if (connection == null) {
// handle error
}
try {
String pwd = hashPassword(password);
String sqlString = “SELECT * FROM db_user WHERE username = '” + username + “’ AND password = '” + pwd + “'”;
Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(sqlString);
if (!rs.next()) {
throw new SecurityException( “User name or password incorrect”);
}
// Authenticated; proceed
} finally {
try {
connection.close();
} catch (SQLException x) {
// forward to handler
}
}
}
如果攻击者能够替代username
和password
中的任意字符串,它们可以使用下面的关于username
的字符串进行SQL注入。
validuser’ OR ‘1’='1
当其注入到命令时,命令就会变成:
SELECT * FROM db_user WHERE username=‘validuser’ OR ‘1’=‘1’ AND password=‘’
同样,攻击者可以为password提供如下字符串。
’ OR ‘1’='1
当其注入到命令时,命令就会变成:
SELECT * FROM db_user WHERE username=‘’ AND password=‘’ OR ‘1’=‘1’
解决思路:
使用java.sql.PreparedStatement
代替java.sql.Statement
,做一个预编译,例如,select * from db_user where username=? and password=?
,然后向PreparedStatement对象中添加?
对应的属性
public void doPrivilegedAction(String username, char[] password) throws SQLException {
Connection connection = getConnection();
if (connection == null) {
// Handle error
}
try {
String pwd = hashPassword(password);
// Ensure that the length of user name is legitimate
if ((username.length() > 8) {
// Handle error
}
String sqlString = “select * from db_user where username=? and password=?”;
PreparedStatement stmt = connection.prepareStatement(sqlString);
stmt.setString(1, username);
stmt.setString(2, pwd);
ResultSet rs = stmt.executeQuery();
if (!rs.next()) {
throw new SecurityException(“User name or password incorrect”);
}
try {
connection.close();
} catch (SQLException x) {
// forward to handler
} finally {
// forward to handler
}
}
3、不安全的随机数
Java API中提供了java.util.Random
类实现PRNG()
,该PRNG是可移植和可重复的,如果两个java.util.Random
类的实例使用相同的种子,会在所有Java实现中生成相同的数值序列。
例如:下面代码片段中,使用了java.util.Random
类,该类对每一个指定的种子值生成同一个序列。
import java.util.Random;
public static void main (String args[]) {
for (int i = 0; i < 10; i++) {
Random random = new Random(123456);
int number = random.nextInt(21);
…
}
}
解决思路:
在安全性要求较高的应用中,应使用更安全的随机数生成器,如java.security.SecureRandom
类。
例如:下面代码片段中,使用java.security.SecureRandom
来生成更安全的随机数。
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public static void main (String args[]) {
try {
SecureRandom random = SecureRandom.getInstance(“SHA1PRNG”);
for (int i = 0; i < 10; i++) {
int number = random.nextInt(21);
…
} catch (NoSuchAlgorithmException nsae) {
…
}
}
使用java.security.SecureRandom
可以确保是真*伪随机数,详见真伪随机数SecureRandom
4、硬编码的密码
程序中采用硬编码方式处理密码,一方面会降低系统安全性,另一方面不易于程序维护。
例如:下列代码中采用硬编码方式处理密码。
public class ConnectionConfig{
String url = “localhost”;
String name = “admin”;
String password = “123456”;
…
}
解决思路:
程序中不应对密码进行硬编码,可以使用配置文件或数据库存储的方式来存储系统所需的数据;并且录入数据时,还可以在对敏感数据做加密处理之后再进行数据的录入,对于双向的加密算法可以解密后判断;对于单向的加密算法,可以通过对输入值的再次加密来判断。
可以构造一个属性文件工具类PropertiesUtil和加解密工具类EncryptUtil,把敏感数据加密后存在属性文件中,需要的时候再解码出来调用。比如可以用jasypt加密密码。
例如:下列代码中从配置文件中获取经过加密的密码值并解密使用。
public class ConnectionConfig{
String url = EncryptUtil.decrypt(PropertiesUtil.get(“connection.url”));
String name = EncryptUtil.decrypt(PropertiesUtil.get(“connection.username”));
String password = EncryptUtil.decrypt(PropertiesUtil.get(“connection.password”));
…
}
jasypt详细使用可查:springboot使用jasypt加密敏感数据
5、SimpleDateFormat的线程不安全
public class DateUtil{
//全局属性 new SimpleDateFormat
private static SimpleDateFormat dateFormatter = new SimpleDateFormat();
/**
- 格式化时间到毫秒级
*/
public static String longDateFormat(Long time, String dateFormat) {
if (time == null) {
return null;
}
dateFormatter.applyPattern(dateFormat);
return dateFormatter.format(new Date(time));
}
}
这样一个时间工具类,在并发场景下,可能会产生线程不安全的情况,即某些调用者线程会读取到意料之外的日期(非自己输入),出现了幻读。
下图来自网络:
写个单元测试看一下效果:
public class test{
@Test
public void test1() {
//创建自定义线程对象
MyThread mt = new MyThread(“新的线程!”);
//开启新线程
mt.start();
//在主方法中执行for循环
for (int i = 0; i < 100; i++) {
logger.info(“main线程!” + i + DateUtil.longDateFormat(1692517360211l,“yyyy-MM-dd HH-mm-ss”));
}
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/87d4e9cbab102dfaa1a0c8d4ec01f3bc.jpeg)
结语
小编也是很有感触,如果一直都是在中小公司,没有接触过大型的互联网架构设计的话,只靠自己看书去提升可能一辈子都很难达到高级架构师的技术和认知高度。向厉害的人去学习是最有效减少时间摸索、精力浪费的方式。
我们选择的这个行业就一直要持续的学习,又很吃青春饭。
虽然大家可能经常见到说程序员年薪几十万,但这样的人毕竟不是大部份,要么是有名校光环,要么是在阿里华为这样的大企业。年龄一大,更有可能被裁。
送给每一位想学习Java小伙伴,用来提升自己。
本文到这里就结束了,喜欢的朋友可以帮忙点赞和评论一下,感谢支持!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
es/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />
结语
小编也是很有感触,如果一直都是在中小公司,没有接触过大型的互联网架构设计的话,只靠自己看书去提升可能一辈子都很难达到高级架构师的技术和认知高度。向厉害的人去学习是最有效减少时间摸索、精力浪费的方式。
我们选择的这个行业就一直要持续的学习,又很吃青春饭。
虽然大家可能经常见到说程序员年薪几十万,但这样的人毕竟不是大部份,要么是有名校光环,要么是在阿里华为这样的大企业。年龄一大,更有可能被裁。
送给每一位想学习Java小伙伴,用来提升自己。
[外链图片转存中…(img-xJn5IAUt-1712709573108)]
本文到这里就结束了,喜欢的朋友可以帮忙点赞和评论一下,感谢支持!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!