目錄
1 身份認證安全... 2
1.1登錄入口... 2
1.1.1密碼強度策略... 2
1.1.2驗證碼生成... 2
1.2.3驗證碼后端校驗... 4
1.2.4認證錯誤限制鎖定... 4
1.2.5短信驗證碼:... 5
1.2會話安全... 6
1.2.1cookie管理... 6
1.2.2 session管理... 6
1.2.3會話重放... 7
2.權限控制—越權漏洞... 8
2.1水平越權:... 8
2.2垂直越權:... 9
3.數據交互安全... 11
3.1跨站腳本攻擊(xss)... 11
3.2sql注入... 13
3.3命令注入... 15
3.4 xml實體注入... 16
3.5文件下載:... 16
3.6文件上傳... 18
3.7跨站請求偽造(csrf)... 20
4.組件安全... 22
4.1 fastjson. 22
4.2 jackson-databind:... 22
4.3 shiro:... 22
4.4 struts2:... 22
1 身份認證安全
1.1登錄入口
1.1.1密碼強度策略
介紹:密碼是驗證的一種方式,當用戶密碼過於簡單,會造成弱口令,導致用戶信息等敏感信息泄露。
審計策略:通過定位登錄入口,查看是否對密碼復雜度進行判斷
代碼規范示例:(此代碼進行口令復雜度校驗,長度大於八位,含有數字,大小寫字母,符號)
function checkPass(pass){
if(s.length < 8){
return 0;}
var ls = 0;
if(s.match(/([0-9])+/)||s.match(/([A-Z])+/)||s.match(/[^a-zA-Z0-9]+/)){ //【正則表達式】
ls++;
}
return ls;
}
if(checkPass(form.password.value)<3){
alert("密碼復雜度不夠,請重新設置!");
form.password.focus();
return false ;
}
代碼規范示例(弱口令檢測)
String pass=request.getParameter("password");
// 對用戶口令基於基本密碼策略+弱口令進行檢測
Map<String,String> map = CheckPassword.isComplex(user, pass, 1);
if(!map.get("returnCode").equals("00")){
String message = map.get("returnMsg");
out.write(message);
response.sendRedirect("Register.jsp?err="+message);
return;
}
1.1.2驗證碼生成
介紹:驗證碼是保護用戶信息安全的一種方式,如果驗證碼校驗簡單,未使用復雜,有干擾的驗證碼,攻擊者會使用工具進行爆破,繞過驗證碼校驗。
審計策略:定位登錄入口,隨機函數是random。可通過關鍵函數搜索。
代碼規范示例:(產生隨機四位驗證碼,其中包含數字字母大小寫,並且有干擾線)
int charNum = 4; // 隨機產生字符數量
if(num != null){
charNum = Integer.parseInt(num);
}
String randString=""; //需要繪制的隨機字符串
// BufferedImage 類描述具有可訪問圖像數據緩沖區的 Image
BufferedImage buffImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR);
Graphics2D g = buffImage.createGraphics()
//設置驗證碼尺寸、背景顏色、字體
//繪制干擾線
/產生隨機/字符集
//繪制干擾線
int lineSize = 100; // 干擾線數量
Random random = new Random();
for (int i = 0; i <= lineSize; i++)
{
int x = random.nextInt(width);
int y = random.nextInt(height);
int xl = random.nextInt(width/8);
int yl = random.nextInt(height/8);
g.setColor(randColor(130, 250));
g.drawLine(x, y, x + xl, y + yl);
}
//產生隨機驗證碼
char[] characterSet ={'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',' Q','R','S','T','U','V','W','X','Y','Z'};
for (int i = 1; i <= charNum; i++){
g.setColor(randColor(20,130));
String rand =String.valueOf(characterSet[random.nextInt(characterSet.length)]); // 獲取隨機的字符
g.translate(random.nextInt(3), random.nextInt(3)); //隨機文字寬度
g.drawString(rand, width/(charNum+2) * i, height/4*3) //繪制位置
randString += rand; //服務器存儲驗證碼
}
session.setAttribute("validateCode", randString); //服務器存儲驗證碼
//禁止圖像緩存
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
1.2.3驗證碼后端校驗
介紹:驗證碼不適用后端校驗,攻擊者可在前端刪除校驗代碼,達到繞過校驗,后端校驗也需要設置驗證碼時效,驗證碼使用一次應及時銷毀。
審計策略:定位登錄入口,進行查看。
代碼規范示例:(清空驗證碼或者重寫驗證碼,一次一驗)
String captcha = request.getParameter("captcha").trim();
HttpSession session = request.getSession();
String imgCode = (String) session.getAttribute("sercurity.captcha");
if (captcha ! = null && !captcha.equals("") && captcha.equalsIgnoreCase(imgCode)) {
//驗證碼為真
// 清空驗證碼或重寫驗證碼
session.setAttribute("sercurity.captcha", null);
//…………省略代碼………………賬戶密碼校驗過程
response.sendRedirect("index.jsp");
return;
}else{ //驗證碼為假
// 清空驗證碼或重寫驗證碼
session.setAttribute("sercurity.captcha", null);
response.sendRedirect("login.jsp?err=captcha_wrong");
return;
}
1.2.4認證錯誤限制鎖定
介紹:用戶認證時間間隔不做限制,攻擊者可無限爆破。
審計策略:定位登錄入口,查看是否對認證時間做限制。
代碼規范示例:(對認證次數以及登錄相隔時間做了限制)
if(!logonDao.getIsRightPassword(un.getPassword())){
// 判斷錯誤次數及登錄相隔時間
long diffMin=(new Date().getTime()-loginLockTime.getTime())/(1000 * 60); // 相隔的時間
if(true == isLock){
if(diffMin > 20){isLock = false;} // 間隔 20 分鍾之后,解鎖賬戶
else{throw new Exception("您在 3 分鍾內登錄錯誤達到 5 次,20 分鍾后再試");}
}
if(diffMin > 3){ // 間隔大於 3 分鍾,清空計數器
loginLockTime = new Date();
failCount = 0;
}
failCount++;
if(failCount >= 5){
isLock = true;
throw new HandlerException("您在 3 分鍾內登錄錯誤達到 5 次,20 分鍾后再試");
}
} // isLock 關聯 IP 和賬戶
1.2.5短信驗證碼:
介紹:短信驗證碼是校驗用戶是否正確的一種方式,如果對發送短信的時間不做限制,攻擊者可以通過腳本執行發送短信,進行短信轟炸,消耗流量。
審計策略:定位登錄入口,查看是否對驗證碼發送時間以及間隔時間做校驗。
安全規范代碼:(手機號后端獲取,發送次數校驗防止轟炸)
if(System.currentTimeMillis()-60000<Long.parseLong(sms_time)){ // 短信驗證碼發送間隔檢查
priDataCache.setParam(“respmsg”, “短信驗證碼已發送,一分鍾后可重新發送");
return -1;
}
String tel=(String)request.getSession().getAttribute(”tel“); //后端獲取發送的手機號碼
(String)request.getSession().setAttribute(“sms_time”, TimeStamp); //標記發送時間
安全規范代碼:(短信提交驗證,時效性,錯誤次數驗證防止爆破)
if(System.currentTimeMillis()-60000>Long.parseLong(sms_time)){ // 短信驗證碼超時檢查
priDataCache.setParam(“sms_yzm”, “”); // 超時清空驗證碼
priDataCache.setParam("sms_yzm_time", "");
priDataCache.setParam("respmsg", "短信驗證碼已經超時,請重新獲取");
return -1;
}
if(sms_input!=null&&sms_input.equals(sms_yzm)){ // 記錄短信驗證碼使用並清空
priDataCache.setParam("sms_yzm", "");
priDataCache.setParam("sms_yzm_time", "");
return 1;
}
if(sms_input!=null){ // 記錄錯誤次數,判斷是否超限
int msgCount = iBaseDao.queryForInt("customer.countMsg", param);
if(msgCount>5){isBeyondCount = true;}
if(isBeyondCount){
TransUtil.responseMessage
(AppConstants.RspCode_FAIL, "錯誤次數過多!",rst);
// return rst 表示存在爆破,調用鎖定邏輯,20 分鍾之后再嘗試
return rst;
1.2會話安全
1.2.1cookie管理
介紹:cookie是存儲在用戶本地的,是文本文件。比如一些瀏覽器的登錄,會記錄用戶密碼,賬號,從而在下次登錄時,不在進行校驗,攻擊者利用人的惰性,劫持用戶cookie,偽造用戶身份進行登錄。從而產生危害。
審計策略:搜索cookie,httponly,secureflag關鍵字,查看是否有有效時間,是否添加對抗xss腳本。
安全代碼示例:
Cookie cookie = new Cookie(map);
cookie.setHttpOnly(true);
cookie.setSecure(true);
response.addCookie(cookie);
response.setHeader("x-xss-protection", "1; mode=block ");
Spring Security 動態添加 Security Headers:
applicationContext-security.xml:
<http>
<!-- ... -->
<headers>
<xss-protection />
</headers>
</http>
1.2.2 session管理
介紹:session是存儲在服務器的,在每次執行操作是都進行一次驗證,看是否具有操作當前數據的權限。用戶的登錄狀態,登錄前后,會話有效期,會話超時,都會進行刷新驗證。
審計策略:搜索關鍵字session。看是否在更新會話是移除舊會話。
代碼規范示例(對會話進行移除刷新)
public HttpSession changeSessionIdentifier(HttpServletRequest request) throws Exception {
HttpSession oldSession = request.getSession();
Map<String,Object> temp = new ConcurrentHashMap<String,Object>();
Enumeration e = oldSession.getAttributeNames();
while (e != null && e.hasMoreElements()) { // 復制會話內容
String name = (String) e.nextElement();
Object value = oldSession.getAttribute(name);
temp.put(name, value);
}
oldSession.invalidate();
HttpSession newSession = request.getSession();
User user = ESAPI.authenticator().getCurrentUser();
user.addSession( newSession );// 更新會話
user.removeSession( oldSession );// 更新會話一定要移除舊會話
// 寫入會話內容
for (Map.Entry<String, Object> stringObjectEntry : temp.entrySet()) newSession.setAttribute(stringObjectEntry.getKey(),stringObjectEntry.getValue());
return newSession;
}
1.2.3會話重放
介紹:會話重放是指攻擊者發送一個目的主機已接受過的包,達到欺騙系統的目的。
審計策略:定位登錄入口,看是否對交互序列號進行判斷。
安全規范代碼:(對會話時效性進行判斷是否為第一次交互,若是創建序列號,存入session告知客戶端,若不是改變交易轉態進行中,業務操作進行中,清空標識,改變狀態)
String transid = request.getParameter("transid");
if(1!==Dao.queryStat(transid)){ // 1:操作可執行 0:操作已完成 -1:操作正在進行 rst.TransUtil.responseErrorMessage(CHECKMSG.OPERTED_ERROR);
return rst;
}else{
String timestamp = (String) session.getAttribute("timestamp");
Map<String, Object> rst = new HashMap<String, Object>();
if (null==timestamp||timestamp.equals("")) {
Date myTimeStamp = new Date().toString();
// 第一次交互在服務端生成時間戳作為標識存入 session
session.setAttribute("timestamp", myTimeStamp);
//時間戳(時效性),唯一ID標記會話(唯一性)
rst.put(“transid”, myTimeStamp);
return rst;
}else
{
String serialNum = (String) request.getParameter("serialNum");
// 第二次交互檢測標識並完成交易,並在交易完成后清空標識
if (null != serialNum && serialNum == timestamp) {
CT.changeTransStat(transid ,-1); // 改變操作狀態
if(transaction(transid)){
session.removeAttribute("timestamp");
CT.changeTransStat(transid,0);// 改變操作狀態
}
return rst;
}
rst= TransUtil.responseErrorMessage(CHECKMSG.CHECK_FAIL);
return rst;
}
2.權限控制—越權漏洞
介紹:服務器端處理請求時,沒有判斷數據所屬人,或判斷操作執行權限,用戶提交的 request 參數(用戶可控),成為了數據獲取或操作執行的主鍵,導致攻擊者可以通過變換鍵值,訪問了他人數據,或執行高權限操作。
常見越權:修改金額,數量,通過用戶id請求用戶信息等。
2.1水平越權:
介紹:同一級別,利用自己身份可以訪問或修改另一用戶信息的操作。
審計策略:在處理用戶操作請求時查看是否有對當前登陸用戶權限做校驗從而確定是否存在漏洞。
漏洞示例:(通過修改請求中的userid,可以刪除任意用戶賬單)
userID=Integer.valueOf( request.getParameter("userid"));
String orderID=request.getParameter("orderid");
JdbcConnection conn = null;
try {
conn = new JdbcConnection();
Object[] params = new Object[2];
params[0] = userID;
params[1] = orderID;
String sql = null;
sql = “delete from user where orderid=? and userid=?”;
Dao.updateUser(sql,params);
}catch{
}
代碼規范示例:(通過session獲取UserId,刪除賬單前判斷用戶是否具有刪除該賬單的權限)
public Object remove(Long addrID){
Map<String, Object> respMap = new HashMap<String, Object>();
if (orderID.isBelong(=(String)request.getSession().getAttribute(”userID"))) {
// 判斷用戶提交的數據是否屬於當前登錄用戶
this.addressService.removeUserAddress(orderID, (String)request.getSession().getAttribute(”userID"));
respMap.put(Constants.RESP_STATUS_CODE_KEY, Constants.RESP_STATUS_CODE_SUCCESS);
respMap.put(Constants.MESSAGE,"地址刪除成功!");
}else{
respMap.put(Constants.RESP_STATUS_CODE_KEY, Constants.RESP_STATUS_CODE_FAIL);
respMap.put(Constants.ERROR,"用戶未登錄,刪除地址失敗!");
}
return respMap;
}
2.2垂直越權:
介紹:低用戶可以訪問高用戶的信息。執行高用戶權限。
審計策越:在處理用戶操作請求時查看是否有對當前登陸用戶權限做校驗從而確定是否存在漏洞。
漏洞示例:(沒有進行身份驗證普通用戶構造deleteUser請求,通過userId可以刪除任意用戶)
@RequestMapping(value = "deleteUser")
public String deleteUser(HttpServletRequest request)throws Exception {
int id = request.getParameter("userid");
try {
userManager.delete(id);
request.setAttribute("msg", "刪除用戶成功");
} catch (ServiceException e) {
// logger.error(e.getMessage(), e);
request.setAttribute("msg", "刪除用戶失敗");
}
return list(request);
}
代碼規范示例:(對當前用戶權限進行校驗,通過sessionid判斷用戶角色)
public class PrivilegeFilter implements Filter{
public void init(FilterConfig config) throws ServletException{
// 獲取資源訪問權限配置
String fileName=config.getInitParameter("privilegeFile");
String realPath=config.getServletContext().getRealPath(fileName);
try{
properties.load(new FileInputStream(realPath));
}
catch(Exception e){
config.getServletContext().log("讀取權限控制文件失敗",e);
}
}
}
代碼規范示例(web.xml中配置過濾器權限)
將權限訪問規則存入 privilege.properties 文件中:
admin.do?action=* = super
list.do?action=add = admin
list.do?action=view = guest
Web.xml 中配置過濾器權限:
<filter>
<filter-name>privilegeFilter</filter-name>
<filter-class>com.filter.privilegeFilter</filter-class>
<init-param>
<param-name>privilegeFile</param-name>
<param-value>/WEB-INF/privilege.properties</param-value>
</init-param>
</filter>
代碼規范示例(基於 URL 和 Method 訪問控制)
Spring Security 提供了基於 URL 和 Method 的訪問控制:
<sec:http>
<sec:intercept-url pattern="/super_portal.do**" access="ROLE_SUPER" />
<sec:intercept-url pattern="/manager_portal.do**" access="ROLE_MANAGER" />
<sec:intercept-url pattern="/**" access="ROLE_USER" />
<sec:form-login />
<sec:logout />
</sec:http>
代碼規范示例(通過sessionid判斷用戶角色)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)throws Exception{
String requestUri=request.getRequestURI().replace(request.getContextPath()+"/", "");
String action=request.getParameter("action");action=action==null?"":action;
String uri=requestUri+"?action="+action;
String role=(String)request.getSession().getAttribute("role");
role=role==null?"guest":role;
boolean authen=false;
for(Object obj:properties.keySet()){
String key=(String)obj;
if(uri.matches(key.replace("?", "\\?").replace(".", "\\.").replace("*", ".*"))){
//判斷用戶是否具有資源的訪問權限
if(role.equals(properties.get(key))){authen=true;break;}
}
}
if(!authen){throw new RuntimeException("您無權訪問該頁面,請以合適的身份登錄后查看。");}
chain.doFilter(request, response);
}
3.數據交互安全
3.1跨站腳本攻擊(xss)
介紹: 由於程序對用戶輸入的內容未做過濾,導致攻擊者構造的代碼在瀏覽器解析執行,產生危害,xss可以進行木馬釣魚,劫持cookie,鍵盤記錄等危害。
審計策略:掃描所有的HttpServletRequest 查看相關的上下文環境。
漏洞示例:(未對輸入進行過濾,校驗,攻擊者可輸入攻擊代碼)
<% out.print(request.getParameter("param")); %>
<script>
function test(){
var str=document.getElementById(“text”).value;
document.getElementById(“t”).innerHTML=“<a href=‘”+str+“’>test</a>";
}
</script>
<div id="t"></div>
<input type = "text" id ="text" value="" />
<input type = "button" id="s" value="write" onclick="test()" />
}
代碼規范示例:(對用戶輸入進行循環過濾,對輸出進行html轉義,)
String btype = ce.XssFliter(request.getParameter(”ip"));
Public String XssFliter (String xss){
Properties prop = System.getProperties();
String os = prop.getProperty("os.name");
String[] vulnchars = {“|”,“;”,“&”,“$”, ”%“, ”@“,”>“,”<“,”`“,”\\“,”\””,”)”,”(”,”+”, ”,”,”,” \r”,};
String[] replacechar = null;
String xsss = xss.tochar();
Boolean needFilter=true;//用以標記
while(needFilter){
for(int i =0;i<xsss.length;i++)
// 遍歷替換,
//如沒有可替換的字符,則設置needFilter為false,不再循環
}
return xsss.toString();
}
代碼規范示例(輸出編碼)
HTML 實體\屬性編碼:
<div>ESAPI.encoder().encodeForHTML($user.name)</div>
<div attr ="\""+ESAPI.encoder().encodeForHTMLAttribute($user.name)"\""/>
JavaScript 編碼:
<script>alert('ESAPI.encoder().encodeForJavaScript ($user.name)')</script>
<script>x=\'ESAPI.encoder().encodeForJavaScript ($user.name)\'</script>
<div onmouseover="x='ESAPI.encoder().encodeForJavaScript ($user.name)'"/>
CSS 編碼:
String safe = ESAPI.encoder().encodeForCSS($user.name));
public class HTMLEntityCodec extends AbstractIntegerCodec
{
……
代碼規范示例(輸出編碼)
public String encode(char[] immune, String input)
{
StringBuilder sb = new StringBuilder();//新建字符序列
for (int offset = 0; offset < input.length();)
{
int point = input.codePointAt(offset);//使用Unicode代碼點進行匹配
if (Character.isValidCodePoint(point)) {
sb.append(encodeCharacter(immune, point));
}
offset += Character.charCount(point);
}
return sb.toString();
}
代碼規范示例(輸出編碼)
public String encodeCharacter(char[] immune, int codePoint)
{
if ((containsCharacter((char)codePoint, immune)) && (Character.isValidCodePoint(codePoint))) {
return new StringBuilder().appendCodePoint(codePoint).toString();
}
String hex = super.getHexForNonAlphanumeric(codePoint);
if ((hex == null) && (Character.isValidCodePoint(codePoint))) {
return new StringBuilder().appendCodePoint(codePoint).toString();
}
if (((codePoint <= 31) && (codePoint != 9) && (codePoint != 10) && (codePoint != 13)) || ((codePoint >= 127) && (codePoint <= 159)))
{
hex = "fffd";
codePoint = 65533;
}
String entityName = (String)characterToEntityMap.get(Integer.valueOf(codePoint));
if (entityName != null) {
return "&" + entityName + ";";
}
return "&#x" + hex + ";";
}
3.2sql注入
介紹:程序員對用戶輸入的內容未做過濾,攻擊者編寫sql語句帶入到數據庫查詢操作,產生危害。
審計策略:這種一般可以直接黑盒找到,如果只是代碼片段快速掃描可控制的參數或者相關的sql關鍵字查看。查看預編譯的完整性,關鍵函數定位setObject()、setInt()、setString()、setSQLXML()關聯上下文搜索set* 開頭的函數。
漏洞示例:(未對輸入內容進行過濾)
select *
from users
where name=‘$UserName' and password=‘$Password ’
安全規范代碼:(對sql常見的字符進行過濾處理)
String btype = ipAdd.SqlFliter(request.getParameter(”ip"));
Public String SqlFliter(String sql){
Properties prop = System.getProperties();
String os = prop.getProperty("os.name");
String[] vulnchars = {“|”,“;”,“&”,“\’", “\”",">","<","`",”=","!"};
String[] vulnwords = {“ net user”,” schema”,… …};
String[] replacechar = null;
String sqls = sql.tochar();
Boolean needFilter=true;//用以標記
while(needFilter){
for(int i =0;i<sqls.length;i++)
// 遍歷替換,如沒有可替換的字符,則設置needFilter為false,不再循環
}
return sqls.toString();
}
代碼規范示例(編碼轉義,特殊字符轉義:)
private String encodeCharacterMySQL( Character c ) {
char ch = c.charValue();
if ( ch == 0x00 ) return “\\0”; //null
if ( ch == 0x08 ) return "\\b"; //BS
if ( ch == 0x09 ) return "\\t"; //TAB
if ( ch == 0x0a ) return "\\n"; //LF
if ( ch == 0x0d ) return "\\r"; //CR
if ( ch == 0x1a ) return "\\Z"; //SUB
if ( ch == 0x22 ) return “\\\”“; //雙引號
if ( ch == 0x25 ) return “\\%”; //百分號
if ( ch == 0x27 ) return “\\’”; //單引號
if ( ch == 0x5c ) return “\\\\”; //反斜杠
if ( ch == 0x5f ) return “\\_”; //下划線
return "\\" + c;
}
代碼規范示例(JDBC預編譯:)
String custname = request.getParameter("customerName");
String query = "SELECT balance FROM user_data WHERE user_name = ? ";
PreparedStatement pstmt = connection.prepareStatement( query );
pstmt.setString( 1, custname); // 通過 setString 傳遞參數,PreparedStatment 會對傳入的參數值做轉義處理
ResultSet results = pstmt.executeQuery( );
Hibernate參數位置綁定:
Query query=session.createQuery(“from User user where user.name=? ”);
query.setString(0, userName);
Mybatis使用#進行:
<!—使用 #{id} 的傳參方式,MyBatis 框架可對傳入的參數值進行,對於不同的數據庫語言有差異,但處理形式相同-->
MySQL:select * from table where name like concat(‘%’,#{name},’%’)
SQL Server:select * from table where name like ‘%’+#{name}+’%’
Oracle:select * from table where name like ‘%’ || #{name} || ’%’
DB2:select * from table where name like concat(‘%’,#{name},’%’)
代碼規范示例(編碼轉義:引號逃逸法:)
String validatedUserId = request.getParameter("userid");
String validatedStartDate = request.getParameter("startdate");
validatedUserId =ESAPI.encoder().encodeForSQL(ORACLE_CODEC,validatedUserId)
validatedUserId= ESAPI.encoder().encodeForSQL(ORACLE_CODEC,validatedStartDate)
public String encodeCharacter( char[] immune, Character c ) {
//全部引號由單引號添為雙引號
if ( c.charValue() == '\’’ )
return "\'\’”;
return ""+c;
}
3.3命令注入
介紹:由於業務需求,程序有可能要執行系統命令的功能,但如果執行的命令用戶可控,業務上有沒有做好限制,就可能出現命令執行漏洞。
審計策略:搜索命令執行的函數Runtime.exec,Process,ProcessBuilder.start,GroovyShell.evaluate,看這些命令是否可控。
漏洞示例:(未對用戶輸入進行過濾,)
String btype = request.getParameter("btype");
String cmds[] = {"cmd.exe",
"/K",
”dir”
+directory};
System.Runtime.getRuntime().exec(cmds);
代碼規范示例:
String btype = ce.CmdFliter(request.getParameter(”ip"));
Public String CmdFliter (String cmd){
Properties prop = System.getProperties();
String os = prop.getProperty("os.name");
String[] vulnchars = {"|",";","&","$",">","<","`","\\","!"};
String[] escapeUnixchars = {"\\|","\\;","\\&","\\$","\\>","\\<","\\`","\\\\","\\!"};
String[] escapeWinchars = {"`|","`;","`&","`$","`>","`<","``","`\\","`!"};
String[] replacechar = null;
String cmds = cmd.tochar();
Boolean needFilter=true;//用以標記
while(needFilter){
for(int i =0;i<cmds.length;i++)
// 遍歷替換,如沒有可替換的字符,則設置needFilter為false,不再循環
}
return cmds.toString();
}
3.4 xml實體注入
介紹:由於解析過程中沒有限制doctype、entity等節點實體的解析,導致的XML外部實體解析漏洞。
審計策略:全局搜索如下字符串StreamSource, XMLConstants, StringReader在項目中搜索. Xsd文件
漏洞示例:
<!DOCTYPE z [<!ENTITY test SYSTEM " file:///etc/passwd/ " >]>
<users>
<a>&test;</a>
<username>Alice</username>
<email>Alice@cmbc</email>
</users>
代碼規范示例:(通過配置解析方式,杜絕非法實體,DOM解析是優先選擇,StAX解析會完全禁止DTD)
DOM 解析:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
dbf.setFeature(FEATURE, true);
SAX 解析:
SAXParserFactory saxpf = SAXParserFactory.newInstance();
String FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
saxpf.setFeature(FEATURE, true);
StAX 解析:
XMLInputFactory factory = XMLInputFactory.newInstance();
factory.setProperty(XMLInputFactory.SUPPORT_DTD, false);
3.5文件下載:
介紹:對用戶的訪問權限不做限制,未過濾../導致用戶可以回溯到任意目錄。下載敏感文件,產生危害。
審計策略:
查找 getPath, getAbsolutePath。
再排查程序的安全策略配置文件,搜索 permission Java.io.FilePermission 字樣和 grant 字樣,防止誤報。換句話說,如果 IO 方案中已經做出防御。只為程序的絕對路徑賦予讀寫權限,其他目錄不賦予讀寫權限。那么目錄系統還是安全的。
漏洞示例:
String imgName = request.getParameter("imgName");
String imgKey = MD5Encrypt.MD5(imgName);//本地
if (imageCache.containsKey(imgKey)) {
data = (byte[]) imageCache.get(imgKey);
} else {
String imagePath = Consts.IMG_LOCAL_PATH + imgName; //直接拼接了本地址和文件名
InputStream inputStream = null;
File imageFile = new File(imagePath);
if (imageFile.exists() && imageFile.isFile()) {
inputStream = new FileInputStream(imagePath);
int i = inputStream.available();
data = new byte[i];
imageCache.put(imgKey, data);
}
}
OutputStream outputStream = response.getOutputStream();
outputStream.write(data);
代碼規范示例:(使用id映射在后端獲取文件地址,文件名)
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
try {
ImageDao imgDao=new ImageDao();
byte data[] = new byte[1];
String imgID = request.getParameter("imgID");
String imgName=imgDao.getImage(imgID);
String imgKey = MD5Encrypt.MD5(imgName);//本地
if (imageCache.containsKey(imgKey)){
data = (byte[]) imageCache.get(imgKey);
}
else{String imagePath = Consts.IMG_LOCAL_PAHT + imgName;}
// 根據文件路徑讀取文件輸出到 data 里
}catch{ // error todo
}
// 將文件內容輸出到客戶端
}
代碼規范示例:(全局過濾../(linux) ..\\(win))
if(request.getParameter("file")!=null){
String context = request.getContextPath();
Boolean flag = false;
int BUFSIZE = 4096;
String filePathOrig = request.getParameter("file");
File file = new File(request.getRealPath(context));
String filePath = file.getParent()+"/upload/"+filePathOrig;
filePath = filePath.replace("..\\",""); //win
filePath = filePath.replace(“../”,“”); //linux、unix
if(filePath != filePathOrig){
return;
}
file = new File(filePath);
// 文件讀取並返回客戶端
}
代碼規范示例(判斷用戶權限,判斷文件類型,是否能夠輸出)
if(request.getParameter("file")!=null){
String context = request.getContextPath();
int BUFSIZE = 4096;
String filePath;
filePath = request.getParameter("file");
File file = new File(request.getRealPath(context));
file = new File(file.getParent()+"/upload/"+filePath);
//判斷請求下載文件是否處於指定的目錄
if(file.getCanonicalPath().startsWith("/var/webapp/upload")==-1){
return;
}
//文件讀取並返回客戶端
}
3.6文件上傳
介紹:對上傳的文件未做校驗,導致攻擊者上傳精心構造的木馬,產生危害。
審計策略:
1:白名單或者黑名單校驗后綴(白名單優先)
2:上傳的文件是否校驗限制了文件的大小(文件太大會造成dos)
3:是否校驗文件上傳的后綴。關鍵函數如下
IndexOf(“.”) 從前往后取第一個點 被繞過可能 1.jpg.jsp
修復方案:IndexOf()替換成lastIndexOf()
4:文件后綴對比
string.equals(fileSuffix)次函數不區分大小寫。可通過string.Jsp這種方式繞過。修復方案在比較之前之前使用 fileSuffix.toLowerCase() 將前端取得的后綴名變換成小寫或者改成s.equalsIgnoreCase(fileSuffix) 即忽略大小
5:是否通過文件類型來校驗
String contentType = file.getContentType();
這種方式可以前端修改文件類型繞過上傳
6、java程序中涉及到文件上傳的函數,比如:
MultipartFile
7、模糊搜索相關文件上傳類或者函數比如
File
FileUpload
FileUtils
UploadHandleServlet
FileLoadServlet
getInputStream
FileOutputStream
DiskFileItemFactory
MultipartRequestEntity
漏洞示例:
String contentType = request.getContentType();
String pLine = new String();
String uploadLocation = new String(UPLOAD_DIRECTORY_STRING);
if (contentType != null && contentType.equals(“image/jpg”) != -1) { //只判斷了contentType
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream()));
pLine = br.readLine();
try {
String filename = pLine.substring(pLine.lastIndexOf("\\"), pLine.lastIndexOf("\""))
BufferedWriter bw = new BufferedWriter(new FileWriter(uploadLocation+filename, true));
for (String line; (line=br.readLine())!=null; )
// 寫文件操作
}
}
代碼規范示例:(對文件類型進行校驗)
public static boolean checkImage(byte[] imgData){
BufferedImage bufferedImage = null;
ByteArrayInputStream bais = new ByteArrayInputStream(imgData);
try {
// 生成 Image 子類對象,方便對圖片判斷
bufferedImage = ImageIO.read(bais);
// 判斷能否獲取有效像素
if(bufferedImage.getHeight()==-1||bufferedImage.getWidth()==-1){
return false;
}
return true;
} catch (IOException e) {
return false;
}
}
代碼規范示例:(自定義補課預測文件名,將文件上傳目錄直接設置為不可執行,移除可執行權限,並二次渲染)
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 獲取文件上傳數據操作…………
// 將上傳文件保存於非解析目錄
String uploadPath ="/var/FileUpload/";
DateFormat df=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// 自定義不可預測文件名
String filename = df.format(new Date())+String.Random(8)+".jpg";
File file = new File(uploadPath+filename);
boolean check = CheckFile.isValid(file);
if(!check){
return;
}
// 寫入文件操作…………
// 移除文件的可執行權限並二次渲染
file.setExecutable(false);
}
3.7跨站請求偽造(csrf)
介紹:跨站請求偽造(Cross-Site Request Forgery,CSRF)是一種使已登錄用戶在不知情的情況下執行某種動作的攻擊。因為攻擊者看不到偽造請求的響應結果,所以CSRF攻擊主要用來執行動作,而非竊取用戶數據。當受害者是一個普通用戶時,CSRF可以實現在其不知情的情況下轉移用戶資金、發送郵件等操作;但是如果受害者是一個具有管理員權限的用戶時CSRF則可能威脅到整個Web系統的安。
審計策略:
此類漏洞一般都會在框架中解決修復,所以在審計csrf漏洞時。首先要熟悉框架對CSRF的防護方案,一般審計時可查看增刪改請求重是否有token、formtoken等關鍵字以及是否有對請求的Referer有進行校驗。手動測試時,如果有token等關鍵則替換token值為自定義值並重放請求,如果沒有則替換請求Referer頭為自定義鏈接或置空。重放請求看是否可以成功返回數據從而判斷是否存在CSRF漏洞。
漏洞示例:
由於開發人員對CSRF的了解不足,錯把“經過認證的瀏覽器發起的請求”當成“經過認證的用戶發起的請求”,當已認證的用戶點擊攻擊者構造的惡意鏈接后就“被”執行了相應的操作。例如,一個博客刪除文章是通過如下方式實現的:
GET http://blog.com/article/delete.jsp?id=102
當攻擊者誘導用戶點擊下面的鏈接時,如果該用戶登錄博客網站的憑證尚未過期,那么他便在不知情的情況下刪除了id為102的文章,簡單的身份驗證只能保證請求發自某個用戶的瀏覽器,卻不能保證請求本身是用戶自願發出的。
代碼規范示例:(使用token解決請求固定問題)
@RequestMapping("updateData")
public boolean updateData(HttpServletRequest req,HttpServletResponse resp){
String referer=req.getHeader("Referer");
// 判斷 Referer 是否以正確域名開頭
if((referer!=null) &&(referer.trim().startsWith("https://www.test.com/"))){
HttpSession s = request.getSession();
String sToken = (String)s.getAttribute("csrftoken");
// 從請求參數中取得 CSRF Token
String pToken = req.getParameter("csrftoken");
if(sToken != null && pToken != null && sToken.equals(pToken)){
// 獲取請求數據並更新
}else{ request.getRequestDispatcher("error.jsp").forward(request,response);
}
}
4.組件安全
4.1 fastjson
修復建議:
將fastjson框架統一更新到1.2.60及以上版本;同時需關閉autotype屬性,將任意版本使用以下代碼請刪除:ParserConfig.getGlobalInstance().setAutoTypeSupport(true)
4.2 jackson-databind:
修復建議:
方式1:關閉Default Typing屬性
方式2:升級版本至jackson-databind >= 2.9.9
4.3 shiro:
修復建議:
方式1:禁用rememberMe功能
方式2:升級版本至shrio version>1.2.4
同時(方式1、方式2均需要進行),為預防第三方開源框架整合shiro,請在源代碼中搜索securityManager.setRememberMeManager(rememberMeManager);、setCipherKey(Base64.decode(" ,如果發現請刪除securityManager.setRememberMeManager(rememberMeManager);或者修改Shrio內置秘鑰key,確保key的唯一性。
4.4 struts2:
修復建議:
方式1:升級版本至struts2>=2.3.35或 struts2>=2.5.17(建議此方案)
方式2:根據自己當前系統版本,進行臨時修復(可作為參考,建議進行升級)
同時,通過組件、框架及其相關漏洞的識別,構建我行軟件資產庫,實現軟件資產識別及軟件資產應急響應,主要包括組件識別、框架識別以及相應漏洞識別等。