| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Java 安全 SDK,提供安全的、常见的 Java 安全编码规范和方法,最大限度避免开发中出现常见的漏洞。
源码完善后上传
通过将恶意的 SQL 语句插入到应用的输入参数中,然后在后台 SQL 数据库上解析执行进行的攻击。 其实现的关键条件为:
在开发中,一般使用预编译的方式进行防御 SQL 注入。当使用者需要自己编写 SQL 语句时,需要注意使用框架提供的方式或者函数实现预编译。并不是使用 ORM 框架后,就不会用 SQL 注入问题。
使用#{}语法时,MyBatis 底层会使用PreparedStatement方法进行参数变量绑定, 可有效防止 SQL 注入
<select id="getById" resultType="org.example.User">
SELECT * FROM user WHERE id = #{id}
</select>使用${}语法时,MyBatis 底层会直接注入原始的字符串,即相当于拼接字符串,因此会导致 SQL 注入
<select id="getByName" resultType="org.example.User">
SELECT * FROM user WHERE name = ${name} limit 1
</select>
相当于
"select * from user Where name = " + name + "limit 1";MyBatis 不支持 else, 需要默认值的情况,可以使用 choose(when, otherwise)
<select id="getUserListSortBy" resultType="org.example.User">
SELECT * FROM user
<if test="sortBy == 'name' or sortBy == 'email'">
order by ${sortBy}
</if>
</select>
<!-- 安全的写法 - 使用参数化查询 -->
<select id="getUsers" resultType="User">
SELECT * FROM users
ORDER BY
<choose>
<when test="orderBy == 'name'">name</when>
<when test="orderBy == 'age'">age</when>
<when test="orderBy == 'email'">email</when>
<otherwise>id</otherwise>
</choose>
<choose>
<when test="direction == 'desc'">DESC</when>
<otherwise>ASC</otherwise>
</choose>
</select>like 语句: 使用 bind 标签来构造新参数,然后再使用 #{}。另外需要过滤通配符等特殊字符,避免 DOS。
<select id="getUserListLike" resultType="org.example.User">
<bind name="pattern" value="'%' + name + '%'" />
SELECT * FROM user
WHERE name LIKE #{pattern}
</select>IN 条件,使用<foreach> 和 #{}
<select id="selectUserIn" resultType="com.example.User">
SELECT * FROM user WHERE name in
<foreach item="name" collection="nameList"
open="(" separator="," close=")">
#{name}
</foreach>
</select> 参数有多个时,一种可以使用@Param("xxx")进行参数绑定,另一种可以通过Map来传参数。
@Param("xxx")方式
List<User> selectByIdSet(@Param("name")String name, @Param("ids")String[] idList);
<select id="selectByIdSet" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
from t_user
WHERE name=#{name,jdbcType=VARCHAR} and id IN
<foreach collection="ids" item="id" index="index"
open="(" close=")" separator=",">
#{id}
</foreach>
</select>Map方式
Map<String, Object> params = new HashMap<String, Object>(2);
params.put("name", name);
params.put("idList", ids);
mapper.selectByIdSet(params);
<select id="selectByIdSet" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_user where
name = #{name}
and ID in
<foreach item="item" index="index" collection="idList" open="(" separator="," close=")">
#{item}
</foreach>
</select>Hibernate 支持 HQL (Hibernate Query Language) 和 native SQL 查询,前者存在 HQL 注入,后者和 JDBC 存在相同的注入问题
Query<User> query = session.createQuery("from User where name = '" + name + "'", User.class);
User user = query.getSingleResult();Query<User> query = session.createQuery("from User where name = ?", User.class);
query.setParameter(0, name);Query<User> query = session.createQuery("from User where name = :name", User.class);
query.setParameter("name", name);
//list
Query<User> query = session.createQuery("from User where name in (:nameList)", User.class);
query.setParameterList("nameList", Arrays.asList("lisi", "zhaowu"));
//Javabean
User user = new User();
user.setName("zhaowu");
Query<User> query = session.createQuery("from User where name = :name", User.class);
// User 类需要有 getName() 方法
query.setProperties(user);String sql = "select * from user where name = '" + name + "'"; Query query = session.createNativeQuery(sql); // Query query = session.createSQLQuery(sql); <deprecated>
String sql = "select * from user where name = :name";
Query query = session.createNativeQuery(sql); // Query query = session.createSQLQuery(sql); <deprecated>
query.setParameter("name", name);
和SQL注入原理一样
拼接用户的查询权限条件
String title = request.getParamenter("name");
MongoCollection<Document> col = mongoClient.getDatabase("MyDB").getCollection("emails");
BasicDBObject query = new BasicDBObject();
query.put("$where", "this.title==\""+ title+"\"");
FindIterable<Document> find = col.find(query);String title = request.getParamenter("name");
MongoCollection<Document> col = mongoClient.getDatabase("MyDB").getCollection("emails");
BasicDBObject query = new BasicDBObject();
query.put("$where", new BasicDBObject("$eq", title));
FindIterable<Document> find = col.find(query);因为前端和服务端没有正确的检验上传的文件内容、类型以及路径是否合法导致
使用File对象的getCanonicalPath方法获取上传文件的实际文件名,若检测到文件名的后缀不是允许的类型(0x00截断,小于JDK1.8),或出现java.io.IOException异常(0x00截断,JDK1.8),或包含冒号(Windows环境中需处理),则说明需要拒绝本次文件上传。
// base/WhiteAndBlackChecker.java
private List<String> arrayBlackList = new ArrayList<String>();
public List<String> getWhiteList() {
return arrayWhiteList;
}
// fileopreate/UploadFileFilter.java
public boolean isValidByWhiteList(File file) throws IOException {
String fileName = file.getCanonicalFile().getName();
int index = fileName.lastIndexOf(".");
String suffix = fileName.substring(index + 1);
return super.getWhiteList().contains(suffix.toLowerCase());
}使用File对象的getCanonicalPath方法获取上传文件的实际路径,和指定目录进行对比,避免使用../ 实现目录穿越
public boolean isValidByAllowedDirectory(File file, String allowedDirectory) throws IOException {
String canonicalPath = file.getCanonicalFile().getPath();
if (System.getProperty("os.name").contains("Window")){
return canonicalPath.toLowerCase().contains(allowedDirectory.toLowerCase());
}else{
return canonicalPath.startsWith(allowedDirectory);
}
}public String generateUniqueFileName(String extName){
long currentTime = System.currentTimeMillis();
int num = (int)(new SecureRandom().nextDouble()*10000);
return currentTime + "" + num + extName;
}服务器没有对下载的文件名和文件路径进行过滤,然而下载的文件名和路径用户是可控的,导致此漏洞存在。
public boolean isValidByAllowedDirectory(File file, String allowedDirectory) throws IOException {
String canonicalPath = file.getCanonicalFile().getPath();
if (System.getProperty("os.name").contains("Window")){
return canonicalPath.toLowerCase().contains(allowedDirectory.toLowerCase());
}else{
return canonicalPath.startsWith(allowedDirectory);
}
}攻击者可以通过漏洞遍历出服务器操作系统中的任意目录文件名,从而导致服务器敏感信息泄漏,某些场景下(如遍历出网站日志、备份文件、管理后台等)甚至可能会导致服务器被非法入侵。
使用File对象的getCanonicalPath方法获得读取文件的实际路径,和指定目录进行对比,避免使用../ 实现目录穿越
/**
* 方式一:检查文件路径是否在允许的目录下
* @param file 文件的对象
* @param allowedDirectory 允许的目录
* @return boolean true为合法,false不合法
* @throws IOException 异常
*/
public boolean isValidByAllowedDirectory(File file, String allowedDirectory) throws IOException {
String canonicalPath = file.getCanonicalFile().getPath();
if (System.getProperty("os.name").contains("Window")){
return canonicalPath.toLowerCase().contains(allowedDirectory.toLowerCase());
}else{
return canonicalPath.startsWith(allowedDirectory);
}
}示例
/**
* 检查文件名中是否包含了空字节,禁止出现%00字符截断
*
* @param file 访问文件
* @return 是否包含空字节
*/
private static boolean nullByteValid(File file) {
return file.getName().indexOf('\u0000') < 1;
}禁止写入如下类型的动态脚本文件:
jsp,jspx,jspa,jspf,asp,asa,cer,aspx,php
WEB-INF/web.xml、/etc/passwd、../../../../../../../etc/passwd
攻击者伪造服务器获取资源的请求,通过服务器来攻击内部系统。比如端口扫描,读取默认文件判断服务架构,或者配合SQL注入等其他漏洞攻击内网的主机
SSRF常出现在URL中,比如分享,翻译,图片加载,文章收藏等功能
白名单为域名
/**
* 通过白名单检查主机名是否可信
* @param url url
* @return true/false
*/
public boolean isValidHostByWhiteList(String url){
URL urlAddress = null;
try {
urlAddress = new URL(url);
} catch (MalformedURLException e) {
logger.warn("非法的URL" + e);
return false;
}
//获取主域名
String topDomain = UrlUtils.getTopDomain(url);
if (topDomain !=null){
for (String s: super.getWhiteList()){
if (topDomain.equals(s)){
return true;
}
}
}
return false;
}
public static String getTopDomain(String url){
try{
String host = new URL(url).getHost().toLowerCase();
//查找倒数第二个.的位置
int index = host.lastIndexOf(".", host.lastIndexOf(".") - 1);
return host.substring(index + 1);
}catch(MalformedURLException e){
logger.warn("非法的URL" + e);
}
return null;
}白名单为允许请求的协议
/**
* 白名单检查请求的协议是否合法
* @param url url
* @return true/false
*/
public boolean isValidProtocolByWhiteList(String url){
try {
URL urlAddress = new URL(url);
if (super.getWhiteList().contains(urlAddress.getProtocol())){
return true;
}
} catch (MalformedURLException e) {
logger.warn("非法的URL" + e);
}
return false;
}黑名单为禁止使用的协议
/**
* 黑名单检查请求的协议是否合法
* @param url url
* @return true/false
*/
public boolean isValidProtocolByBlackList(String url){
try {
URL urlAddress = new URL(url);
if (!super.getBlackList().contains(urlAddress.getProtocol())){
return true;
}
} catch (MalformedURLException e) {
logger.warn("非法的URL" + e);
}
return false;
}协议白名单结合黑名单内网IP并判断302跳转
/**
* 推荐的检测组合,白名单结合内网IP并判断302跳转
* @param url 需要检测的url
* @return boolean
*/
public boolean checkUrl(String url){
HttpURLConnection httpURLConnection;
String finalUrl = url;
try {
do{
//判断协议和是否是内网IP
if (!isValidProtocolByWhiteList(url) && !isInnerIp(url)){
return false;
}
//处理302跳转
httpURLConnection = (HttpURLConnection) new URL(finalUrl).openConnection();
httpURLConnection.setInstanceFollowRedirects(false);//不跟随跳转
httpURLConnection.setUseCaches(false);//不使用缓存
httpURLConnection.setConnectTimeout(5*1000);//设置超时时间
httpURLConnection.connect();//发送dns请求
int statusCode = httpURLConnection.getResponseCode(); //获取状态码
if(statusCode>=300 && statusCode<=307 && statusCode!=304 && statusCode!=306){
String redirectedURL = httpURLConnection.getHeaderField("Location");
if(null==redirectedURL){
break;
}
finalUrl = redirectedURL;//获取跳转之后的url,再次进行判断
}else{
break;
}
}while (httpURLConnection.getResponseCode()!=HttpURLConnection.HTTP_OK);//如果没有返回200,则继续对跳转后的链接进行检查
httpURLConnection.disconnect();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
/**
* 判断是否为内网IP
* @param url 请求的url
* @return boolean
*/
private static boolean isInnerIp(String url){
try {
URI uri = new URI(url);
String host = uri.getHost();
InetAddress inetAddress = InetAddress.getByName(host);
//获取IP
String ip = inetAddress.getHostAddress();
//内网IP段
String[] blackSubnetList = {"10.0.0.0/8","172.16.0.0/12","192.168.0.0/16","127.0.0.0/8"};
for (String subnet: blackSubnetList){
SubnetUtils subnetUtils = new SubnetUtils(subnet);
if (subnetUtils.getInfo().isInRange(ip)){
return true; //如果IP段在内网中,返回
}
}
} catch (URISyntaxException | UnknownHostException e) {
logger.warn("解析错误uri " + e);
}
return false;
}后台服务器在告知浏览器跳转时,未对客户端传入的重定向地址进行合法性校验,导致用户浏览器跳转到钓鱼页面的一种漏洞。
/**
* 检查跳转的URL是否在白名单上
* @param url 检测的URL
* @return boolean
*/
public boolean isWhiteList(String url){
//只允许http, https
String u = url.toLowerCase().trim();
if (!Pattern.matches("^https?.*$", u)){
return false;
}
URI uri = null;
try {
uri = new URI(u);
String host = uri.getHost();
//获取主域名
String topDomain = UrlUtils.getTopDomain(u);
List<String> whiteList = super.getWhiteList();
//如果域名在白名单或者主域名在白名单
if (whiteList.contains(host) || whiteList.contains(topDomain)){
return true;
}
} catch (URISyntaxException e) {
logger.warn("解析url错误:" + e );
}
return false;
}通过统一的跳转风险提示页面,让用户选择是否跳转。
命令执行漏洞是指应用有时需要调用一些执行系统命令的函数,如果系统命令代码未对用户可控参数进行过滤,则当用户能控制这些函数的参数时,就可以将恶意系统命令拼接到正常命令中,从而造成命令执行工具。
危害:
public void test(HttpServletRequest request){
String ip = request.getParameter("ip");
//不加过滤直接拼接到执行的命令中
String exec = "ping "+ip;
ProcessBuilder p = null;
BufferedReader reader = null;
try {
//调用shell进行执行
p = new ProcessBuilder("bash","-c",exec);
p.start();
String line;
reader = new BufferedReader(new InputStreamReader(p.start().getInputStream(),"GBK"));
while((line=reader.readLine())!=null){
//System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}通过精确匹配用户输入的数据,不符合一律不执行,其次可以通过白名单方式匹配允许执行的命令。具体实现见源码exec/ExecCmdFilter.java
public void setRegex(String regex) {
this.regex = regex;
}
public String getRegx() {
return regex;
}
/**
* 判断用户提供的命令是否合法
* @param cmd 需要检测的命令
* @return boolean 是否合法
*/
private String regex;
public boolean isValidCMD(String cmd){
String processedCmd = cmd.trim();
//精确匹配拼接用户输入的数据
//正则表达式为自定义
Boolean isMatch = Pattern.matches(regex, processedCmd);
Boolean isWhite = super.getWhiteList().contains(processedCmd);
return isMatch || isWhite;
}
//测试代码
public class ExecCmdFilterTest {
@Test
public void testIsValidCMD(){
ExecCmdFilter cmdFilter = ExecCmdFilter.getInstance();
//匹配IP的正则表达式
String regex = "((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))";
cmdFilter.setRegex(regex);
String cmd = "127.0.0.1;ls";
if (cmdFilter.isValidCMD(cmd)){
System.out.println("执行的命令合法");
}else{
System.out.println("执行的命令不合法");
}
}
}Cross-Site Scripting(跨站脚本攻击)简称 XSS,是一种代码注入攻击。攻击者通过在目标网站上注入恶意脚本,使之在用户的浏览器上运行。利用这些恶意脚本,攻击者可获取用户的敏感信息如 Cookie、SessionID 等,进而危害数据安全。
注入的方法
目前主流最新版浏览器对内置了预防XSS的措施。防御XSS的核心就是对不可信数据进行正确的编码。所以只有在正确的地方使用正确的编码才能消除XSS漏洞。
java工程中,常用的转义库为 org.owasp.encoder
//插入不可信数据到HTML标签之间时,进行HTML Entity编码
String encodedContent = ESAPI.encoder().encodeForHTML(request.getParameter(“input”));
//插入不可信数据到HTML属性里时,进行HTML属性编码
String encodedContent = ESAPI.encoder().encodeForHTMLAttribute(request.getParameter(“input”));
//插入不可信数据到SCRIPT里时,进行JavaScript编码
String encodedContent = ESAPI.encoder().encodeForJavaScript(request.getParameter(“input”));
//插入不可信数据到Style属性里时,进行CSS编码
String encodedContent = ESAPI.encoder().encodeForCSS(request.getParameter(“input”));
//插入不可信数据到HTML URL里时,进行URL编码
//当需要往HTML页面中的URL里插入不可信数据的时候,需要对其进行URL编码,如下:
//<a href=”http://www.abcd.com?param=…插入不可信数据前,进行URL编码…”> Link Content </a>
String encodedContent = ESAPI.encoder().encodeForURL(request.getParameter(“input”));产生SpEL表达式注入漏洞的大前提是存在SpEL的相关库。产生SpEL表达式注入漏洞主要原因是,很大一部分开发人员未对用户输入进行处理就直接通过解析引擎对SpEL继续解析。一旦用户能够控制解析的SpEL语句,便可以通过反射的方式构造执行的命令,从而达到RCE的目的。
示例:
String spel = "T(java.lang.Runtime).getRuntime().exec(\"calc\")";
ExpressionParser parser = new SpelExpressionParser();
Student student = new Student();
EvaluationContext context =SimpleEvaluationContext.forReadOnlyDataBinding().withRootObject(student).build();
Expression expression = parser.parseExpression(spel);
System.out.println(expression.getValue(context));在web.xml中定义error-page,防止当出现错误时暴露服务器信息。
| Back | FazBrowse Home | New Git URL |