1. 領域驅動安全 php
領域驅動安全是一種代碼設計方法。其思想是將一個隱式的概念轉化爲顯示,我的認爲便是面向對象的方法,將一個概念抽象成一個類,在該類中經過方法對類的屬性進行約束。是不是字符串,包含什麼字母等。對原始的字符串進行封裝。 java
好處是隻須要對類作單元測試,而且保證在代碼中須要用到該隱式概念時都建立這個類來進行處理。最重要的是將輸入數據固定位數據。就使用參數化語句進行預處理。參數化語句是將sql語句中的參數用佔位符替代,在數據庫完成編譯工做只籤參數的時候,程序再調用相應接口將參數傳遞給數據庫。因此這樣就能夠保證用戶輸入的數據就必定是數據不會被誤當成sql語句來執行。可是有不少不能用預處理的狀況 mysql
對於不能使用預處理的狀況,如limit,常在分頁,搜索等業務邏輯中都會用到。則不能使用預處理查詢,須要對輸入數據進行編碼或者嚴格的過濾。好比數字就只能是0-9,採用白名單原則進行過濾。 android
ps:參數化只能參數化數據部分,沒法對關鍵字和標示符來進行參數化。這是預處理的侷限,從攻擊者來看,應尋找沒法使用預處理的點來進行測試。從防來講,就得用編碼,字符過濾,正則匹配,白名單黑名單策略來對輸入進行過濾。 程序員
2. 各個語言的預處理 web
(1) java預處理: 正則表達式
java鏈接數據庫有兩個用法,第一是經過jdbc+sql語句的方式來鏈接操做數據庫。或者經過Hibernat輕量級映射框架來鏈接操做數據庫。 算法
兩種方式的理解: spring
1. 經過jdbc+sql的方式用於小項目,能夠靈活的控制業務邏輯。可是會特別冗餘,dao層會出現不少冗餘的sql語句。使得代碼變得不易維護和debug。可是如今都是使用spring框架。獨立架構DAO層根據本身的業務來封裝持久層的操做。此種作法比較多。 sql
如下是一個完整的預處理查詢方法,未經封裝.
public ResultSet prepareQuery(String sql,Object []param){
int num=findNum(sql);
try{
getconnection();
pst=connection.prepareStatement(sql);
for(int i=1;i<=num;i++)
pst.setObject(1,param[i-1]);
res=pst.executeQuery();
}catch(Exception e){
System.out.println(e.getMessage());
}
return res;
}
2. 也有經過hibernat來經過對象映射數據庫。其實hibernat是對jdbc的近一步封裝,它是一種輕量級框架不須要像struts那種繼承某個類或者接口之類的工做,在配置上相對簡單明瞭。它用本身的映射配置文件來代替了jdbc+sql的模式。主要有*.properties,*cfg.xml核心配置文件,*hbm.xml映射文件和.java的映射類組成。但直接使用hibernat會形成查詢攻擊問題。雖然它也有本身的預處理。
package com.liang.hibernate;
import java.util.Date;
//建立映射類
public class User {
private String id;
private String name;
private String password;
private Date createTime;
private Date expireTime;
}
//提供User.hbm.xml文件,完成實體類映射
<span style="font-size:12px;"><?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<!--生成默認爲user的數據庫表-->
<class name="com.liang.hibernate.User">
<id name="id">
<!-- 算法的核心思想是結合機器的網卡、當地時間一個隨機數來生成GUID --
<gnerator class="uuid"></generator>
</id>
<property name="name"></property>
<property name="password"></property>
<property name="createTime" type="date"></property>
<property name="expireTime" type="date"></property>
</class>
</hibernate-mapping></span>
將User.hbm.xml文件加入到hibernate.cfg.xml文件中
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3..dtd
<hibernate-configuration>
<session-factory>
<!-- 設置數據庫驅動 -->
<propertyname="hibernate.connection.driver_class">com.mysql.jdbc.Driver<property>
<!-- 設置數據庫URL -->
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_first</property>
<!-- 數據庫用戶名 -->
<property name="hibernate.connection.username">root</property>
<!-- 數據庫密碼 -->
<property name="hibernate.connection.password">123456</property>
<!-- 指定對應數據庫的方言,hibernate爲了更好適配各類關係數據庫,針對每種數據庫都指定了一個方言dialect -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 映射文件 -->
<mapping resource="com/liang/hibernate/User.hbm.xml"/>
</session-factory>
</hibernate-configuration>
編寫工具類ExportDB.java,將hbm生成ddl,也就是hbm2ddl
package com.liang.hibernate;
import org.hibernate.cfg.Configuration;
import org.hibernate.tool.hbm2ddl.SchemaExport;
/**
* 將hbm生成ddl
* @author liang
*
*/
public class ExportDB{
public static void main(String[]args){
//默認讀取hibernate.cfg.xml文件
Configuration cfg = new Configuration().configure();
////生成並輸出sql到文件(當前目錄)和數據庫
SchemaExport export = new SchemaExport(cfg);
export.create(true, true);
}
}
測試以前,要提早創建數據庫hibernate_first,測試以下:
控制檯打印的SQL語句:
drop table if exists User
create table User (id varchar(255) not null, name varchar(255), password varchar(255), createTime date, expireTime date, primary key (id)
(5)創建客戶端類Client,添加用戶數據到mySQL
package com.liang.hibernate;
import java.util.Date;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
public class Client {
public static void main(String[]args){
//讀取hibernate.cfg.xml文件
Configuration cfg = new Configuration().configure();
//創建SessionFactory
SessionFactory factory =cfg.buildSessionFactory();
//取得session
Session session = null;
try{
//開啓session
session = factory.openSession();
//開啓事務
session.beginTransaction();
User user = new User();
user.setName("jiuqiyuliang");
user.setPassword("123456");
user.setCreateTime(new Date());
user.setExpireTime(new Date());
//保存User對象
session.save(user);
//提交事務
session.getTransaction().commit();
}catch(Exception e){
e.printStackTrace();
//回滾事務
session.getTransaction().rollback();
}finally{
if(session != null){
if(session.isOpen()){
//關閉session
session.close();
}
}
}
}
}
預處理查詢:
string sql="select * from users where username=? and password=?"
Query lookupUser=session.createQuery(sql);
lookupUser.setString(0,username);
lookipUser.setString(1,password);
List rs=lookupUser.list();
右鍵debug運行,測試完成以後,咱們查詢一下測試結果:
五、爲了在調試過程當中能觀察到Hibernate的日誌輸出,最好加入log4j.properties配置文件、在CLASSPATH中新建log4j.properties配置文件或將該配置文件拷貝到src下,便於程序調試。
3. 內容以下:
<span style="font-size:12px;">### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p c{1}:%L - %m%n
### direct messages to file hibernate.log ###
#log4j.appender.file=org.apache.log4j.FileAppender
#log4j.appender.file.File=hibernate.log
#log4j.appender.file.layout=org.apache.log4j.PatternLayout
#log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
### set log levels - for more verbose logging change 'info' to 'debug'###
log4j.rootLogger=warn, stdout</span>
(2).NET(C#)
與java的語法大體相同
Sqlconnection sql=new sqlconnection(connectionString)//獲取鏈接
String sql="select * from userswhere @username=@username"cmd=new Sqlcommand(Sql,con); //用佔位符代替參數的sql語句
cmd.Parament.Add("參數名",數據類型,長度)//添加參數屬性
cmd.Parament.value["@username"]=username; //添加參數屬性
reader=cmd.executeReader();//執行sql
(2) PHP的參數化語句(mysqli,PEAR:MDB2,PDO)
mysqli包
$con=new mysqli("localhost","username","password","db");
$sql="select * from users where username=? and password=?";
$cmd=$con->prepare($sql);
//將參數添加到sql查詢中
$cmd->bind_param("ss",$username,$password);
$cmd->execute();
或PHP5.5後更簡單的預處理 $result=pg_query_params("SELECT * FROM users WHERE username=$1 AND password=$2",Array($username,$password));
PEAR:MDB2
$mdb2=&MDB2::factory($dsn);
$sql="SELECT * FROM users WHERE username=? AND password=?";
$types=array('text','text');
$cmd=$mdb->prepare($sql,$types,MDS2_PREPARE_MANIP);
$data=array($username,$password);
$result=$cmd->execute($data);
PDO
$sql="SELECT * FROM uses WHERE username=:username AND"+"password=:password";
$stmt=$dbh->prepare($sql);
//綁定值和數據類型
$stmt->bindParam(':username',$username,PDO::PARAM_STR,12);//佔位符,值,類型,長度
$stmt->bindParam(':username',$username,PDO::PARAM_STR,12);
$stmt->execute();
除了以上的web開發語言,還有IOS,android,H5均可以對數據庫進行預處理操做。
3.輸入驗證
白名單:
白名單原則須要考慮如下因素,已知的值,數據類型,數據大小,數據範圍,數據內容。這幾個方面最好都獲得約束來限制輸入數據,對於大字符級的限制則稍微困難。可是sql注入主要是英文字母或者百分號和數字。
儘可能使用白名單,在客戶端瀏覽器的安全過濾不可靠,由於數據會被篡改。能夠在WAF層使用黑白名單驗證。保證參數化語句的使用。數據庫進行編碼,讀取數據編碼。
(1) 用已知值:造成列表與輸入值進行校驗,在數據庫中能夠describe tablename;來獲取該表的列,若須要對列參數化卻沒法預處理的時候即可以經過此方法。這是針對列表名狀況。
(2) 間接輸入:在接受時只傳輸數據對應的索引,在銀行業務中經常使用這種方式,針對用戶卡號,不直接傳輸卡號,而是傳輸數據庫的索引,經過列表庫再對應其卡號。若被操做索引則會形成嚴重影響
黑名單:
黑名單即過濾不安全的字符,也能夠利用正則表達式,'|%|--|;|/\*|\\\*|_|\[@|xp_
不能孤立使用黑名單,由於非安全字符多而複雜,沒法及時更新。儘管使用了參數化查詢也不能沒有輸入過濾。由於參數化會致使不安全字符直接被注入到數據庫,引發二階注入等其餘問題。
PS:對於驗證失敗的處理,恢復或者錯誤,恢復不太可取,黑客會構造讓過濾器沒法迭代引發的多字符攻擊,錯誤就是將頁面重定向到一個錯誤頁面,影響了用戶體驗,可是能夠在前臺加上驗證,保證真正的用戶不會被重定向到錯誤頁面。
java中的數據輸入驗證
JSF框架,一個沒有流行起來的框架。自帶一些驗證機制。相似開發桌面程序同樣開發web
String regEx="[^a-zA-Z0-9]"; Pattern p=Pattern.compile(regEx);
if(username!=null){ m=p.matcher(username);
username=m.replaceAll("").trim(); } else if(password!=null){
m=p.matcher(password); password=m.replaceAll("").trim(); }
全部程序的驗證主要是經過正則表達式來完成驗證,只是調用的方法不一樣。
PHP中的輸入驗證:
preg_match(regex,matchstring):使用正則表達式regex對matchstring執行正則表達式匹配,
is_<type>(input):檢查輸入是否爲<type>,如is_numeric
strlen(input):檢查輸入的長度。
Demo
$username=$_POST['username'];
if(!preg_match("/^[a-zA-Z] {8,12}$/D",$username){
//驗證失敗
}
通常php框架thinkphp中都不須要用這種方式,而是使用I(input)函數進行過濾,而且其全局過濾是默認進行的。I函數最後一個參數是過濾方法,能夠對IP,EMAIL,表達式等進行過濾,全局過濾器var_filters是一個可迭代的過濾器。只有當最後一個參數爲NULL時,纔是不進行任何過濾。
編碼:
沒法使用預處理的地方使用編碼進行過濾
針對ORACLE:將單個單引號'替換成兩個'',在PL/SQL中須要替換成4個'''',由於單引號是做爲字符串結束符,須要一個引用符。使用dbms-assert也可進行輸入驗證。提供7個函數驗證不一樣的輸入
excute immediate 'select' || sys.dbms_assert.SIMPLE_SQL_NAME(FIELD) || 'from' || (sys.dbms_assert.SCHEMA_NAME(OWNER),FALSE) || '.' ||sys.dbms._assert.QUALIFIED_SQL_NAME(TABLE);
dbms_assert提供的函數
針對SQLserver:select * from userinfo where username like 'a\%' escape '\'
escape()指定轉義字符,其餘的也是替換單引號或者用eacape轉義單引號可是在esacpe的過程當中escape自己就會被單引號所污染變成字符串,因此該作法不可取,PL/SQL與oracle同樣。
針對mysql:也是對'進行\'轉義,存儲過程當中replace(@sql,'\'','\\\'')
整體來說,在對輸入進行處理的須要注意一下幾點:
1. 使用參數化預處理
2. 對敏感字符進行編碼轉義
3. 對輸入進行白名單黑名單結合過濾
4. 對非數據的關鍵字和標示符在進入數據庫前利用數據庫函數和自定義函數進行驗證。
5. 總體項目使用統一化的編碼。
6. 在網絡層經過WAF對攻擊進行過濾攔截
在設計上避免sql注入
1. 經過存儲過程訪問數據庫:
在數據庫訪問中,由程序構造的sql語句傳入數據庫執行,這樣的動態sql語句擁有比存儲過程更大的權限,因此能夠只對應用程序提供存儲過程的接口,而後存儲過程不能對數據庫數據進行insert,update,delete操做。也就減輕了sql注入的影響。並且這樣攻擊者就沒法調用系統的存儲過程,對提權和內網滲透將更加困難。
缺點就是到處使用存儲過程編程開發將很是繁瑣,須要架構一個完善且實用的持久層比較複雜。
2. 使用抽象層訪問數據庫:
Hibernate,AciveRecord,Entity Framework,ADO.net,JDBC,PDO這樣的抽象層訪問數據庫,程序員無需編寫sql語句,經過參數來傳遞進行與數據庫的數據交換。
3.處理敏感數據下降風險
在數據庫中的口令,用戶我的信息,等感敏數據應隔離保存,處理這些敏感數據時與正常有所不一樣。
口令:對用戶密碼等信息採用SHA256等單向加鹽不可逆算法進行加密,在登錄過程當中將明文加密與密文進行比對來完成驗證,而且將salt與哈希分開保存。相似這樣的敏感信息能夠採起如上方式。
財務信息:使用FIPS認證後的算法進行加密,
存檔:按期存檔和清除數據庫信息
避免明顯的對象名:如password就叫password,kennword(德),Motdepass,mdp(法),Wachtwoord(荷蘭),Senha(葡萄牙),Haslo(波蘭)
honeypot:蜜罐,建立假信息表引誘攻擊,將第一時間通知管理員
參數化語句是經過預編譯實現參數化,因此關鍵字和標示符沒法在編譯後再插入
在應用程序中使用白名單,在web防火牆中使用黑名單
安全防護平臺WAF
1. 可配置規則集:擁有本身的指令來配置防護策略,如正則匹配,黑白名單等
2. 請求覆蓋範圍:針對cookie,url,request heard,等進行保護,覆蓋範圍大
3. 針對編碼攻擊url,WAF提供大量編碼轉換函數來應對,並匹配每一個規則,支持LUA語言來自定義規則
4. 響應分析:根據規則集帶外規則匹配異常響應,阻塞其返回給用戶
5. 入侵檢測:經過日誌來對sql注入以後的詳細信息作檢查,該過程不依賴web應用程序。
鎖定應用程序數據:
1. 使用較低權限登錄數據庫:
2. 隔離數據庫登錄:將不一樣應用程序的權限設定死
3. 撤銷public許可
4. 使用存儲過程
5. 使用加密技術
高風險數據庫函數與存儲過程