Java安全編碼之使用者輸入
0x00 安全引言
1、傳統Web應用與新興移動應用
(1)傳統Web應用:瀏覽器 HTTP 伺服器
(2)新興移動應用:APP HTTP 伺服器
從安全形度看,傳統Web應用與新興移動應用沒有本質區別
2、Web應用安全的核心問題是什麼?
使用者提交的資料不可信是Web應用程式核心安全問題
使用者可以提交任意輸入
例如:
√ 請求引數->多次提交或者不提交
√ 修改Cookie
√ 修改HTTP資訊頭
√ 請求順序->跳過或者打亂
3、Web應用防禦
(1)完善的異常處理
(2)監控
(3)日誌:記錄重要業務、異常的詳細請求資訊
4、對輸入的處理
建議採用:白名單
儘量避免:淨化或黑名單
0x01 SQL隱碼攻擊
1、原理:
(1)合法輸入:
#!sql
id=1
SELECT * FROM users WHRER id='1';
(2)惡意注入:
#!sql
id=1' or '1'='1
SELECT * FROM users WHRER id='1' or 'a'='a';
2、Java程式碼分析(JDBC)
(1)不合規程式碼(SQL引數拼接)
#!java
public class SQLInject {
public static void main(String[] args)throws Exception{
//正常輸入
select("1");
// 惡意輸入
select("' or 'a'='a");
}
public static void select(String id){
//宣告Connection物件
Connection con;
//驅動程式名
String driver = "com.mysql.jdbc.Driver";
//URL指向要訪問的資料庫名mydata
String url = "jdbc:mysql://localhost:3306/mybatis";
//MySQL配置時的使用者名稱
String user = "root";
//MySQL配置時的密碼
String password = "budi";
//遍歷查詢結果集
try {
//載入驅動程式
Class.forName(driver);
//1.getConnection()方法,連線MySQL資料庫!!
con = DriverManager.getConnection(url,user,password);
if(!con.isClosed())
System.out.println("Succeeded connecting to the Database!");
//2.建立statement類物件,用來執行SQL語句!!
Statement statement = con.createStatement();
//要執行的SQL語句
String sql = "select * from users where id='"+id+"'";
//3.ResultSet類,用來存放獲取的結果集!!
ResultSet rs = statement.executeQuery(sql);
System.out.println("-----------------");
System.out.println("執行結果如下所示:");
System.out.println("-----------------");
String age,name;
while(rs.next()){
//獲取stuname這列資料
name = rs.getString("name");
//獲取stuid這列資料
age = rs.getString("age");
//輸出結果
System.out.println(name + "\t" + age);
}
rs.close();
con.close();
} catch(ClassNotFoundException e) {
//資料庫驅動類異常處理
System.out.println("Sorry,can`t find the Driver!");
e.printStackTrace();
} catch(SQLException e) {
//資料庫連線失敗異常處理
e.printStackTrace();
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
System.out.println("資料庫資料成功獲取!!");
}
}
}
執行結果:
#!shell
SQL Paramter:1
-----------------
budi 27
-----------------
SQL Paramter:' or 'a'='a
-----------------
budi 27
budisploit 28
-----------------
(2)合規程式碼(引數化查詢)
#!java
public class SQLFormat {
public static void main(String[] args)throws Exception{
select("1");
select("' or 'a'='a");
}
public static void select(String id){
//宣告Connection物件
Connection con;
//驅動程式名
String driver = "com.mysql.jdbc.Driver";
//URL指向要訪問的資料庫名mydata
String url = "jdbc:mysql://localhost:3306/mybatis";
//MySQL配置時的使用者名稱
String user = "root";
//MySQL配置時的密碼
String password = "budi";
//遍歷查詢結果集
try {
//載入驅動程式
Class.forName(driver);
//1.getConnection()方法,連線MySQL資料庫!!
con = DriverManager.getConnection(url,user,password);
if(!con.isClosed())
System.out.println("Succeeded connecting to the Database!");
//2.//要執行的SQL語句
String sql = "select * from users where id=?";
//3.建立statement類物件,ResultSet類,用來存放獲取的結果集!!
PreparedStatement stmt = con.prepareStatement(sql);
stmt.setString(1, id);
ResultSet rs = stmt.executeQuery();
System.out.println("-----------------");
System.out.println("執行結果如下所示:");
System.out.println("-----------------");
String age,name;
while(rs.next()){
//獲取stuname這列資料
name = rs.getString("name");
//獲取stuid這列資料
age = rs.getString("age");
//輸出結果
System.out.println(name + "\t" + age);
}
rs.close();
con.close();
} catch(ClassNotFoundException e) {
//資料庫驅動類異常處理
System.out.println("Sorry,can`t find the Driver!");
e.printStackTrace();
} catch(SQLException e) {
//資料庫連線失敗異常處理
e.printStackTrace();
}catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
System.out.println("資料庫資料成功獲取!!");
}
}
}
執行結果:
#!shell
SQL Paramter:1
-----------------
budi 27
-----------------
SQL Paramter:' or 'a'='a
-----------------
-----------------
3、防範建議:
√ 採用引數查詢即預編譯方式(首選)
√ 字串過濾
0x02 XML注入
1、原理
(1)合法輸入:
#!xml
quantity=1
<item>
<name>apple</name>
<price>500.0</price>
<quantity>1</quantity>
<item>
(2)惡意輸入:
#!xml
quantity=1</quantity><price>5.0</price><quantity>1
<item>
<name>apple</name>
<price>500.0</price>
<quantity>1</quantity><price>5.0</price><quantity>1</quantity>
<item>
2、Java程式碼分析
(1)不合規程式碼(未進行安全檢查)
#!java
public class XMLInject2 {
public static void main(String[] args) {
// 正常輸入
ArrayList<Map<String, String>> normalList=(ArrayList<Map<String, String>>)
ReadXML("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\normal.xml","price");
System.out.println(normalList.toString());
// 異常輸入
ArrayList<Map<String, String>> evilList=(ArrayList<Map<String, String>>)
ReadXML("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\evil.xml","price");
System.out.println(evilList.toString());
}
private static List<Map<String,String>> ReadXML(String uri,String NodeName){
try {
//建立一個解析XML的工廠物件
SAXParserFactory parserFactory=SAXParserFactory.newInstance();
//建立一個解析XML的物件
SAXParser parser=parserFactory.newSAXParser();
//建立一個解析助手類
MyHandler myhandler=new MyHandler(NodeName);
parser.parse(uri, myhandler);
return myhandler.getList();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
執行結果:
#!shell
正常輸入結果:[{price=500.0}]
惡意輸入結果:[{price=500.0}, {price=5.0}]
(2)合規程式碼(利用schema安全檢查)
#!xml
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="item">
<xs:complexType>
<xs:sequence>
<xs:element name="name" type="xs:string"/>
<xs:element name="price" type="xs:decimal"/>
<xs:element name="quantity" type="xs:integer"/>
</xs:sequence>
</xs:complexType>
</xs:element>
測試程式碼
#!java
public class XMLFormat{
public static void main(String[] args) {
//測試正常輸入
test("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\normal.xml");
//測試異常輸入
test("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\evil.xml");
}
private static void test(String file) {
SchemaFactory schemaFactory = SchemaFactory
.newInstance("XMLConstants.W3C_XML_SCHEMA_NS_URI");
Schema schema;
try {
schema = schemaFactory.newSchema(new File("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\inject\\schema.xsd"));
Validator validator = schema.newValidator();
validator.setErrorHandler(new ErrorHandler() {
public void warning(SAXParseException exception)
throws SAXException {
System.out.println("警告:" + exception);
}
public void fatalError(SAXParseException exception)
throws SAXException {
System.out.println("致命:" + exception);
}
public void error(SAXParseException exception) throws SAXException {
System.out.println("錯誤:" + exception);
}
});
validator.validate(new StreamSource(new File(file)));
System.out.println("解析正常");;
} catch (SAXException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
System.out.println("解析異常");
} catch (IOException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
System.out.println("解析異常");
}
}
}
執行結果:
#!shell
正常輸入........
解析正常
惡意輸入........
錯誤:org.xml.sax.SAXParseException; systemId: file:/D:/JavaWorkspace/TestInput/src/cn/com/budi/xml/inject/evil.xml; lineNumber: 7; columnNumber: 10; cvc-complex-type.2.4.d: 發現了以元素 'price' 開頭的無效內容。此處不應含有子元素。
3、防範建議:
√ 文件型別定義(Document Type Definition,DTD)
√ XML結構化定義檔案(XML Schemas Definition)
√ 白名單
0x03 XXE (XML external entity)
1、原理:
(1)合法輸入:
#!xml
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE updateProfile [<!ENTITY lastname "Hello, Budi!">
<!ENTITY file SYSTEM "file:///D:/test.txt">]>
<users >
<firstname>&file</firstname>
<lastname>&lastname;</lastname>
</users>
(2)惡意輸入:
#!xml
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///D:/password.txt"> ]>
<users >
<firstname>&file;</firstname>
<lastname>&lastname;</lastname>
</users>
2、Java程式碼分析
(1)不合規程式碼(未安全檢查外部實體)
#!java
public class XXEInject {
private static void receiveXMLStream(InputStream inStream, MyDefaultHandler defaultHandler) {
// 1.獲取基於SAX的解析器的例項
SAXParserFactory factory = SAXParserFactory.newInstance();
// 2.建立一個SAXParser例項
SAXParser saxParser = factory.newSAXParser();
// 3.解析
saxParser.parse(inStream, defaultHandler);
}
public static void main(String[] args) throws FileNotFoundException, ParserConfigurationException, SAXException, IOException{
//正常輸入
receiveXMLStream(new FileInputStream("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\xxe\\inject\\normal.xml"),
new MyDefaultHandler());
//惡意輸入
receiveXMLStream(new FileInputStream("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\xxe\\inject\\evil.xml"),
new MyDefaultHandler());
}
}
執行結果:
#!shell
正常輸入,等待解析......
<firstname>XEE TEST !!</firstname>
==========================
惡意輸入,等待解析......
<firstname>OWASP BWA root/owaspbwa
Metasploitable msfadmin/msfadmin
Kali Liunx root/wangpeng
</firstname>
(2)合規程式碼(安全檢查外部實體)
#!java
public class CustomResolver implements EntityResolver{
public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException{
//System.out.println("PUBLIC:"+publicId);
//System.out.println("SYSTEM:"+systemId);
System.out.println("引用實體檢測....");
String entityPath = "file:///D:/test.txt";
if (systemId.equals(entityPath)){
System.out.println("合法解析:"+systemId);
return new InputSource(entityPath);
}else{
System.out.println("非法實體:"+systemId);
return new InputSource();
}
}
}
測試程式碼
#!java
public class XXEFormat {
private static void receiveXMLStream(InputStream inStream, MyDefaultHandler defaultHandler) {
// 獲取基於SAX的解析器的例項
SAXParserFactory factory = SAXParserFactory.newInstance();
// 建立一個SAXParser例項
SAXParser saxParser;
try {
saxParser = factory.newSAXParser();
//建立讀取工具
XMLReader reader = saxParser.getXMLReader();
reader.setEntityResolver(new CustomResolver());
reader.setErrorHandler(defaultHandler);
InputSource is = new InputSource(inStream);
reader.parse(is);
System.out.println("\t成功解析完成!");
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
System.out.println("\t非法解析!");
} catch (SAXException e) {
// TODO Auto-generated catch block
System.out.println("\t非法解析!");
} catch (IOException e) {
// TODO Auto-generated catch block
System.out.println("\t非法解析!");
}
}
public static void main(String[] args) throws ParserConfigurationException, SAXException, IOException{
//正常輸入
System.out.println("正常輸入,等待解析......");
receiveXMLStream(new FileInputStream("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\xxe\\inject\\normal.xml"),
new MyDefaultHandler());
System.out.println("==========================");
//惡意輸入
System.out.println("惡意輸入,等待解析......");
receiveXMLStream(new FileInputStream("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\xml\\xxe\\inject\\evil.xml"),
new MyDefaultHandler());
}
}
執行結果:
#!shell
正常輸入,等待解析......
引用實體檢測....
合法解析:file:///D:/test.txt
成功解析完成!
==========================
惡意輸入,等待解析......
引用實體檢測....
非法實體:file:///D:/password.txt
非法解析!
3、防範建議:
√ 白名單
0x04命令注入
1、原理:
(1)正常輸入:
#!shell
dir
(2)惡意輸入:
#!shell
dir & ipconfig & net user budi budi /add & net localgroup Administrators admin /add
2、Java程式碼分析
(1)非合規Window命令注入
#!java
public class OrderWinFault {
public static void main(String[] args) throws Exception{
//正常命令
runOrder("dir");
//惡意命令
runOrder("dir & ipconfig & net user budi budi /add & net localgroup Administrators admin /add");
}
private static void runOrder(String order) throws IOException, InterruptedException{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("cmd.exe /C "+order);
int result = proc.waitFor();
if(result !=0){
System.out.println("process error: "+ result);
}
InputStream in = (result == 0)? proc.getInputStream() : proc.getErrorStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
StringBuffer buffer=new StringBuffer();
String line;
while((line = reader.readLine())!=null){
buffer.append(line+"\n");
}
System.out.print(buffer.toString());
}
}
(2)非合規的Linux注入命令
#!java
public class OrderLinuxFault {
public static void main(String[] args) throws Exception{
// 正常命令
runOrder("ls");
// 惡意命令
runOrder(" ls & ifconfig");
}
private static void runOrder(String order) throws IOException, InterruptedException{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec(new String [] {"sh", "-c", "ls "+order});
int result = proc.waitFor();
if(result !=0){
System.out.println("process error: "+ result);
}
InputStream in = (result == 0)? proc.getInputStream() : proc.getErrorStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
StringBuffer buffer=new StringBuffer();
String line;
while((line = reader.readLine())!=null){
buffer.append(line+"\n");
}
System.out.print(buffer.toString());
}
}
(3)合規編碼(對命令安全檢查)
#!java
public class OrderFormat {
public static void main(String[] args) throws Exception{
runOrder("dir");
runOrder("dir & ipconfig & net user budi budi /add & net localgroup Administrators admin /add");
}
private static void runOrder(String order) throws IOException, InterruptedException{
if (!Pattern.matches("[0-9A-Za-z@.]+", order)){
System.out.println("存在非法命令");
return;
}
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("cmd.exe /C "+order);
int result = proc.waitFor();
if(result !=0){
System.out.println("process error: "+ result);
}
InputStream in = (result == 0)? proc.getInputStream() : proc.getErrorStream();
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
StringBuffer buffer=new StringBuffer();
String line;
while((line = reader.readLine())!=null){
buffer.append(line+"\n");
}
System.out.print(buffer.toString());
}
}
3、防範建議:
√ 白名單
√ 嚴格許可權限制
√ 採用命令標號
0x05 壓縮炸彈(zip bomb)
(1)合法輸入:
#!shell
普通壓縮比檔案normal.zip
(2)惡意輸入:
#!shell
高壓縮比檔案evil.zip
2、Java程式碼分析
#!java
public class ZipFault {
static final int BUFFER = 512;
public static void main(String[] args) throws IOException{
System.out.println("正常壓縮檔案.......");
checkzip("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\zip\\normal.zip");
System.out.println("惡意壓縮檔案.......");
checkzip("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\zip\\evil.zip");
}
private static void checkzip(String filename) throws IOException{
BufferedOutputStream dest = null;
FileInputStream fls = new FileInputStream(filename);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fls));
ZipEntry entry;
long begin = System.currentTimeMillis();
while ((entry = zis.getNextEntry()) != null){
System.out.println("Extracting:" + entry+"\t解壓後大小:"+entry.getSize());
int count;
byte data[] = new byte[BUFFER];
FileOutputStream fos = new FileOutputStream("D:/"+entry.getName());
dest = new BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER))!=-1){
dest.write(data,0, count);
}
dest.flush();
dest.close();
}
zis.close();
long end = System.currentTimeMillis();
System.out.println("解壓縮執行耗時:" + (end - begin) + " 豪秒");
}
}
執行結果:
#!shell
正常壓縮檔案.......
Extracting:normal.txt 解壓後大小:17496386
解壓縮執行耗時:382 豪秒
惡意壓縮檔案.......
Extracting:evil.txt 解壓後大小:2000000000
解壓縮執行耗時:25911 豪秒
(2)合規程式碼
#!java
public class ZipFormat {
static final int BUFFER = 512;
static final int TOOBIG = 0x640000;
public static void main(String[] args) throws IOException{
checkzip("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\zip\\normal.zip");
checkzip("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\zip\\evil.zip");
}
private static void checkzip(String filename) throws IOException{
BufferedOutputStream dest = null;
FileInputStream fls = new FileInputStream(filename);
ZipInputStream zis = new ZipInputStream(new BufferedInputStream(fls));
ZipEntry entry;
long begin = System.currentTimeMillis();
while ((entry = zis.getNextEntry()) != null){
System.out.println("Extracting:" + entry+"\t解壓後大小:"+entry.getSize());
if (entry.getSize() > TOOBIG){
System.out.println("壓縮檔案過大");
break;
}
if (entry.getSize() == -1){
System.out.println("檔案大小異常");
}
int count;
byte data[] = new byte[BUFFER];
FileOutputStream fos = new FileOutputStream("D:/"+entry.getName());
dest = new BufferedOutputStream(fos, BUFFER);
while ((count = zis.read(data, 0, BUFFER))!=-1){
dest.write(data,0, count);
}
dest.flush();
dest.close();
}
zis.close();
long end = System.currentTimeMillis();
System.out.println("解壓縮執行耗時:" + (end - begin) + " 豪秒");
}
}
執行結果:
#!shell
正常檔案.........
Extracting:normal.txt 解壓後大小:17496386
解壓縮執行耗時:378 豪秒
===================
惡意檔案.........
Extracting:evil.txt 解壓後大小:2000000000
壓縮檔案過大
解壓縮執行耗時:0 豪秒
3、防範建議:
√ 解壓前檢查解壓後檔案大小
0x06 正規表示式注入
1、原理:
(1)合法輸入
#!shell
search=error
拼接後
#!shell
(.*? +public\\[\\d+\\]+.*error.*)
(2)惡意輸入
#!shell
search=.*)|(.*
拼接後
#!shell
(.*? +public\\[\\d+\\]+.*.*)|(.*.*)
2、Java程式碼分析
(1)非合規程式碼(未進行安全檢查)
#!java
public class RegexFault {
/**
* 以行為單位讀取檔案,常用於讀面向行的格式化檔案
*/
public static void readFileByLines(String filename,String search) {
File file = new File(filename);
BufferedReader reader = null;
String regex ="(.*? +public\\[\\d+\\] +.*"+search+".*)";
System.out.println("正規表示式:"+regex);
try {
reader = new BufferedReader(new FileReader(file));
String tempString = null;
int line = 1;
System.out.println("查詢開始......");
// 一次讀入一行,直到讀入null為檔案結束
while ((tempString = reader.readLine()) != null) {
//System.out.println("line " + line + ": " + tempString);
if(Pattern.matches(regex, tempString)){
// 顯示行號
System.out.println("line " + line + ": " + tempString);
}
line++;
}
reader.close();
System.out.println("查詢結束....");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
}
}
public static void main(String[] args){
//正常輸入
readFileByLines("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\regex\\regex.log","error");
//惡意輸入
readFileByLines("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\regex\\regex.log",".*)|(.*");
}
}
執行結果:
#!shell
正常輸入......
正規表示式:(.*? +public\[\d+\] +.*error.*)
line 5: 10:48:08 public[48964] Backup failed with error: 19
============================
惡意輸入......
正規表示式:(.*? +public\[\d+\] +.*.*)|(.*.*)
line 1: 10:47:03 private[423] Successful logout name: budi ssn: 111223333
line 2: 10:47:04 public[48964] Failed to resolve network service
line 3: 10:47:04 public[1] (public.message[49367]) Exited with exit code: 255
line 4: 10:47:43 private[423] Successful login name: budisploit ssn: 444556666
line 5: 10:48:08 public[48964] Backup failed with error: 19
(2)合規程式碼(進行安全檢查)
#!java
public class RegexFormat {
/**
* 檢測是否存在非法字元
* @param search
*/
private static boolean validate(String search){
for (int i = 0; i< search.length(); i++){
char ch = search.charAt(i);
if(!(Character.isLetterOrDigit(ch) || ch ==' ' || ch =='\'')){
System.out.println("存在非法字元,查詢失敗....");
return false;
}
}
return true;
}
/**
* 以行為單位讀取檔案,常用於讀面向行的格式化檔案
*/
public static void readFileByLines(String filename,String search) {
if(!validate(search)){
return;
}
File file = new File(filename);
BufferedReader reader = null;
String regex ="(.*? +public\\[\\d+\\] +.*"+search+".*)";
System.out.println("正規表示式:"+regex);
try {
reader = new BufferedReader(new FileReader(file));
String tempString = null;
int line = 1;
System.out.println("查詢開始......");
// 一次讀入一行,直到讀入null為檔案結束
while ((tempString = reader.readLine()) != null) {
//System.out.println("line " + line + ": " + tempString);
if(Pattern.matches(regex, tempString)){
// 顯示行號
System.out.println("line " + line + ": " + tempString);
}
line++;
}
reader.close();
System.out.println("查詢結束....");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
}
}
public static void main(String[] args){
//正常輸入
System.out.println("正常輸入......");
readFileByLines("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\regex\\regex.log","error");
System.out.println("============================");
//惡意輸入
System.out.println("惡意輸入......");
readFileByLines("D:\\JavaWorkspace\\TestInput\\src\\cn\\com\\budi\\regex\\regex.log",".*)|(.*");
}
}
執行結果:
#!shell
============================
正常輸入......
正規表示式:(.*? +public\[\d+\] +.*error.*)
line 5: 10:48:08 public[48964] Backup failed with error: 19
============================
惡意輸入......
存在非法字元,查詢失敗....
3、防範建議:
√ 白名單
0x07 未淨化輸入
(1)日誌記錄
正常輸入:
#!shell
budi
日誌記錄:
#!shell
User Login Successed for: budi
惡意輸入:
#!shell
budi \nUser Login Successed for: administrator
日誌記錄:
#!shell
User Login Failed for: budi
User Login Successed for: administrator
(2)更新使用者名稱
正常輸入:
#!sql
username=budi
SQL查詢:
#!sql
SELECT * FROM users WHRER id='budi';
惡意輸入:
#!sql
username=budi' or 'a'='a
SQL查詢:
#!sql
SELECT * FROM users WHRER id='budi' or 'a'='a';
2、Java程式碼分析
(1)非合規程式碼(未安全檢查)
#!java
public class LogFault {
private static void writeLog( boolean isLogin,String username){
if(isLogin){
System.out.println("User Login Successed for: "+username);
}else{
System.out.println("User Login Failed for: "+username);
}
}
public static void main(String[] args){
String test1= "budi";
System.out.println("正常使用者登入成功後,記錄日誌.....");
//正常使用者登入成功後,記錄日誌
writeLog(true, test1);
//惡意使用者登入失敗,記錄日誌
String test2 = "budi \nUser Login Successed for: administrator";
System.out.println("惡意使用者登入失敗,記錄日誌.....");
writeLog(false, test2);
}
}
執行結果:
#!shell
正常使用者登入成功後,記錄日誌.....
User Login Successed for: budi
惡意使用者登入失敗,記錄日誌.....
User Login Failed for: budi
User Login Successed for: administrator
(2)合規程式碼(安全檢查)
#!java
public class LoginFormat {
private static void writeLog( boolean isLogin,String username){
if(!Pattern.matches("[A-Za-z0-9_]+", username)){
System.out.println("User Login Failed for Unknow User");
}else if(isLogin){
System.out.println("User Login Successed for: "+username);
}else{
System.out.println("User Login Failed for: "+username);
}
}
public static void main(String[] args){
String test1= "budi";
System.out.println("正常使用者登入成功後,記錄日誌.....");
writeLog(true, test1);
String test2 = "budi \nUser Login Successed for: administrator";
System.out.println("惡意使用者登入失敗,記錄日誌.....");
writeLog(false, test2);
}
}
執行結果:
#!shell
正常使用者登入成功後,記錄日誌.....
User Login Successed for: budi
惡意使用者登入失敗,記錄日誌.....
User Login Failed for Unknow User
3、防範建議:
√ 先檢測使用者輸入,強烈建議直接拒絕帶非法字元的資料
0x08 路徑遍歷
1、原理:
(1)正常輸入:
#!shell
john.txt
(2)惡意輸入:
#!shell
../../a.txt"
2、Java程式碼分析
(1)非合規程式碼(未安全檢查)
#!java
public class PathFault {
public static void main(String[] args) throws IOException{
System.out.println("合法輸入.......");
readFile("john.txt");
System.out.println("\n惡意輸入.......");
readFile("../../a.txt");
}
private static void readFile(String path) throws IOException{
File f = new File("F://passwords//"+path);
String absPath = f.getAbsolutePath();
FileOutputStream fls = new FileOutputStream(f);
System.out.print("絕對路徑:"+absPath);
if(!isInSecureDir(Paths.get(absPath))){
System.out.println("->非安全路徑");
return;
}
System.out.print("->安全路徑");
}
private static boolean isInSecureDir(Path path){
if(!path.startsWith("F://passwords//")){
return false;
};
return true;
}
}
執行結果:
#!shell
合法輸入.......
絕對路徑:F:\passwords\john.txt->安全路徑
惡意輸入.......
絕對路徑:F:\passwords\..\..\a.txt->安全路徑
(2)合規程式碼(先統一路徑表示)
#!java
public class PathFormat {
public static void main(String[] args) throws IOException{
System.out.println("合法輸入.......");
readFile("john.txt");
System.out.println("/n惡意輸入.......");
readFile("../../a.txt");
}
private static void readFile(String path) throws IOException{
File f = new File("F://passwords//"+path);
String canonicalPath = f.getCanonicalPath();
System.out.println("絕對路徑"+canonicalPath);
FileInputStream fls = new FileInputStream(f);
if(!isInSecureDir(Paths.get(canonicalPath))){
System.out.print("非安全路徑");
return;
}
System.out.print("安全路徑");
}
private static boolean isInSecureDir(Path path){
if(!path.startsWith("F://passwords//")){
return false;
};
return true;
}
}
執行結果:
#!shell
合法輸入.......
絕對路徑F:\passwords\john.txt->安全路徑
惡意輸入.......
絕對路徑F:\a.txt->非安全路徑
3、防範建議
√ 嚴格的許可權限制->安全管理器
√ getCanonicalPath()在所有平臺上對所有別名、快捷方式、符號連結採用統一的解析。
0x09 格式化字串
1、原理:
(1)正常輸入:
11
正常拼接:
#!shell
System.out.printf("11 did not match! HINT: It was issued on %1$te rd of some month\n", c);
(2)惡意輸入:
#!shell
%1$tm或%1$te或%1$tY
惡意拼接:
#!shell
System.out.printf("%1$tm did not match! HINT: It was issued on %1$te rd of some month\n", c);
2、Java程式碼分析
(1)非合規程式碼:
#!java
public class DateFault {
static Calendar c = new GregorianCalendar(2016, GregorianCalendar.MAY, 23);
public static void main(String[] args){
//正常使用者輸入
System.out.println("正常使用者輸入.....");
format("11");
System.out.println("非正常輸入獲取月份.....");
format("%1$tm");
System.out.println("非正常輸入獲取日.....");
format("%1$te");
System.out.println("非正常輸入獲取年份.....");
format("%1$tY");
}
private static void format(String month){
System.out.printf(month+" did not match! HINT: It was issued on %1$te rd of some month\n", c);
}
}
執行結果:
#!shell
11 did not match! HINT: It was issued on 23rd of some month
非正常輸入獲取月份.....
05 did not match! HINT: It was issued on 23rd of some month
非正常輸入獲取日.....
23 did not match! HINT: It was issued on 23rd of some month
非正常輸入獲取年份.....
2016 did not match! HINT: It was issued on 23rd of some month
(2)合規程式碼:
#!java
public class DateFormat {
static Calendar c = new GregorianCalendar(2016, GregorianCalendar.MAY, 23);
public static void main(String[] args){
//正常使用者輸入
System.out.println("正常使用者輸入.....");
format("11");
System.out.println("非正常輸入獲取月份.....");
format("%1$tm");
System.out.println("非正常輸入獲取日.....");
format("%1$te");
System.out.println("非正常輸入獲取年份.....");
format("%1$tY");
}
private static void format(String month){
System.out.printf("%s did not match! HINT: It was issued on %1$te rd of some month\n", month, c);
}
}
執行結果:
#!shell
正常使用者輸入.....
11 did not match! HINT: It was issued on
Exception in thread "main" java.util.IllegalFormatConversionException: e != java.lang.String
3、防範建議:
√ 對使用者輸入進行安全檢查
√ 在格式字串中,杜絕使用使用者輸入引數
0x0A 字串標準化
1、原理:
(1)合法輸入:
#!shell
username=budi
(2)惡意輸入一:
#!shell
username=/><script>alert(1)</script>
username=/\uFE65\uFE64script\uFE65alert(1) \uFE64/script\uFE65
(3)惡意輸入二:
#!shell
username=A\uD8AB
username=A?
2、Java程式碼分析
(1)非合規程式碼(先檢查再統一編碼)
#!java
public class EncodeFault {
public static void main(String[] args){
System.out.println("未編碼的非法字元");
check("/><script>alert(2)</script>");
System.out.println("Unicode編碼的非法字元");
check("/\uFE65\uFE64script\uFE65alert(1) \uFE64/script\uFE65");
}
public static void check(String s){
Pattern pattern = Pattern.compile("[<>]");
Matcher matcher = pattern.matcher(s);
if (matcher.find()){
System.out.println(s+"->存在非法字元");
}else{
System.out.println(s+"->合法字元");
}
s = Normalizer.normalize(s, Form.NFC);
}
}
執行結果:
#!shell
未編碼的非法字元
/><script>alert(2)</script>->存在非法字元
Unicode編碼的非法字元
/﹥﹤script﹥alert(1) ﹤/script﹥->合法字元
(3)合規程式碼(先統一編碼再檢查)
#!java
public class EncodeFormat {
public static void main(String[] args){
System.out.println("未編碼的非法字元");
check("/><script>alert(2)</script>");
System.out.println("Unicode編碼的非法字元");
check("/\uFE65\uFE64script\uFE65alert(1)\uFE64/script\uFE65");
}
public static void check(String s){
s = Normalizer.normalize(s, Form.NFC);
// 用\uFFFD替代非Unicode編碼字元
s = s.replaceAll("^\\p{ASCII}]", "\uFFFD");
Pattern pattern = Pattern.compile("[<>]");
Matcher matcher = pattern.matcher(s);
if (matcher.find()){
System.out.println(s+"->存在非法字元");
}else{
System.out.println(s+"->合法字元");
}
}
}
執行結果:
#!shell
未編碼的非法字元
/><script>alert(2)</script>->存在非法字元
Unicode編碼的非法字元
/><script>alert(1)</script>->存在非法字元
3、防範建議:
√ 先按指定編碼方式標準化字串,再檢查非法輸入
√ 檢測非法字元
0x0B 最後總結
- 從安全形度看,移動應用與傳統Web應用沒有本質區別。
- 安全的Web應用必須處理好兩件事:
- 處理好使用者的輸入(HTTP請求)
- 處理好應用的輸出(HTTP響應)
參考文獻: 《Java安全編碼標準》
相關文章
- java安全編碼指南之:輸入校驗2020-09-21Java
- java安全編碼指南之:輸入注入injection2020-10-12Java
- java安全編碼指南之:字串和編碼2020-09-16Java字串
- 編寫安全的驅動程式之輸入輸出檢查2011-07-11
- java安全編碼指南之:方法編寫指南2020-10-08Java
- java安全編碼指南之:Number操作2020-09-10Java
- java安全編碼指南之:基礎篇2020-08-25Java
- java安全編碼指南之:執行緒安全規則2020-10-23Java執行緒
- java安全編碼指南之:檔案IO操作2020-10-27Java
- java安全編碼指南之:ThreadPool的使用2020-10-20Javathread
- java安全編碼指南之:序列化Serialization2020-11-01Java
- java安全編碼指南之:堆汙染Heap pollution2020-09-18Java
- java安全編碼指南之:Mutability可變性2020-09-03Java
- java安全編碼指南之:異常處理2020-09-29Java
- java安全編碼指南之:宣告和初始化2020-09-06Java
- java安全編碼指南之:死鎖dead lock2020-10-01Java
- java安全編碼指南之:敏感類的拷貝2020-09-28Java
- java安全編碼指南之:敏感類的複製2020-09-28Java
- Java__包機制__使用者輸入2021-03-13Java
- java安全編碼指南之:可見性和原子性2020-09-25Java
- java安全編碼指南之:鎖的雙重檢測2020-10-14Java
- java安全編碼指南之:Thread API呼叫規則2020-10-19JavathreadAPI
- Web前端安全之安全編碼原則2021-10-20Web前端
- 直播app原始碼,Java的輸入輸出2023-01-05APP原始碼Java
- java安全編碼指南之:檔案和共享目錄的安全性2020-11-03Java
- java安全編碼指南之:lock和同步的正確使用2020-10-10Java
- hal 編碼器做使用者輸入時捕獲初值的設定2022-01-08
- 體面編碼之Java2018-12-31Java
- Java IO輸入輸出及亂碼問題2018-08-22Java
- [java IO流]之編碼和解碼2021-02-10Java
- Vim中輸入法與編碼設定2010-10-21
- java編碼之間轉換2011-12-20Java
- Java 輸入輸出流2018-12-23Java
- JAVA輸入輸出流2015-08-04Java
- [java IO流]之 基本資料型別輸入輸出流2021-02-10Java資料型別
- linux 指令碼切換使用者不用輸入密碼2010-04-20Linux指令碼密碼
- 網站一開啟就顯示windows安全,輸入使用者名稱和密碼2014-04-14網站Windows密碼
- Java之Base64編碼解析2018-12-01Java