** Sql injection 필요조건
1. 사용자의 입력 값을 필터링, 인코딩 없이 그대로 서버 페이지에서 사용한다.
2. 그 입력 값을 이용해서 서버 페이지는 쿼리를 수행한다.
3. 명령 실행을 위해 사용되는 SQL 서버 계정이 SA일 경우, 피해는 더 커질 수 있다.
--상세한 방법
http://www.taeyo.net/Lecture/NET/Secure_Injection.asp
http://www.unixwiz.net/techtips/sql-injection.html
http://www.securiteam.com/securityreviews/5DP0N1P76E.html
http://www.spidynamics.com/papers/SQLInjectionWhitePaper.pdf
** SQL injection 사전방지
- 사용자의 입력은 결코 신뢰하지 않는다
- 정규 표현식을 사용하여 적법한 입력 외에는 거부한다.
- 저장 프로시저를 사용한다(웹페이지에서 문자열 동적 쿼리는 피한다)
* 최대 문자열의 길이나 타입을 한정한다?
- Sysadmin 계정이 아닌 최소 권한 계정으로 데이터베이스에 연결하게 한다(sa계정을 사용하지 않는다)
- sql 쉘 실행권한을 없앤다. (주기적인 확인필요, 공격시 쉘권한을 복구하여 실행하는경우가 있음)
- 데이터베이스 연결 문자열을 가급적 config 파일에 저장하지 않는다(asp.net의 경우 web.config에 암호화하여 지정)
- 공격자에게 너무 많은 오류 정보를 노출하지 않는다
** SQL injection 방법과 대책
* 웹페이지에서 동적으로 쿼리 빌드 시, 문자열 연결이 아닌 매개변수 쿼리를 사용조작 및 주석처리화 및 에러분석가능
1. 사용자 인증공격 : 정상적인 sql을 변조하여 비정상적으로 통과
** 방식
입력창 : http://duck/index.asp?category=food' or 1=1--
처리되는 쿼리 : SELECT * FROM product WHERE PCategory='food' or 1=1--'
** 로그인경우
rs = select * from user_table where id='ID' and passwd='PWD'
if rs==1
//성공시
else
//실패시.
==> select * from user_table where id='admin' and passwd='' or 'x'='x';
: true, false, true => 결론적으로 true
** 대책
- 사용자의 모든입력값 및 url인자값에 대하여 특수문자 필터링(',<,>,;,--)
- 사용자의 모든 입력값, 크기 및 url인자값에 대하여 sql명령어 금지("/\;:Space--+등과 or, and, union, select, update, insert 등검사)
=> 해당변수의 특수문제 제거및 변형 Request.QueryString["ID"].ToString().Replace("'","\"");
//입력값일 경우 client 자바스크립트로 처리 실제
//한글포함여부
function is_han(val) { //한글이 하나라도 섞여 있으면 true를 반환
var judge = false;
for(var i = 0; i < val.length; i++) {
var chr = val.substr(i,1);
chr = escape(chr);
if (chr.charAt(1) == "u") {
chr = chr.substr(2, (chr.length - 1));
if((chr >= "3131" && chr <= "3163") || (chr >= "AC00" && chr <= "D7A3")) {
judge = true;
break;
}
}
else judge = false;
}
return judge;
}
//한글만 입력가능
function han_only(val) { //한글로만 되있으면 true를 반환 = 영어, 숫자, 특수문자가 있으면 false
var judge = false;
for(var i = 0; i < val.length; i++) {
var chr = val.substr(i,1);
chr = escape(chr);
if (chr.charAt(1) == "u") {
chr = chr.substr(2, (chr.length - 1));
if((chr >= "3131" && chr <= "3163") || (chr >= "AC00" && chr <= "D7A3")) judge = true;
}
else {
judge = false;
break;
}
}
return judge;
}
//영문만 입력확인
function eng_only(val) { //영어로만 되있으면 true를 반환 = 한글, 숫자, 특수문자가 있으면 false
var re = /^[A-Za-z]+$/g;
var rs = re.test(val);
return rs;
}
//이메일주소확인
function is_email(str) { //규정에 맞는 email 주소인지 체크
var r1 = new RegExp("(@.*@)|(\\.\\.)|(@\\.)|(^\\.)");
var r2 = new RegExp("^.+\\@(\\[?)[a-zA-Z0-9\\-\\.]+\\.([a-zA-Z]{2,3}|[0-9]{1,3})(\\]?)$");
return (!r1.test(str) && r2.test(str));
}
//숫자만 입력 확인
function is_number(str) {
var r = new RegExp("^[0-9]+$");
return r.test(str);
}
//전화번호 입력 확인
function is_phone(str) {
var r = new RegExp("^[0-9]{2,4}-[0-9]{2,4}-[0-9]{4,4}$");
return r.test(str);
}
//공백제거
function trim(str) { //trim()함수 구현
var newStr = str.replace(/^\s+/,"").replace(/\s+$/,"");
return newStr;
}
//엔터키처리
function enter_key(form) { //enter key를 눌렀을 때 submit
//처럼 사용한다
if (event.keyCode ==13) {
form.submit();
}
}
//날짜형식확인
function is_date(datein){ // 입력날짜의 기본값은 mm/dd/yy이고 다른 형식이면 arguments를 줘야한다.
var type = isDate.arguments[1];
var rval = false;
var indate=datein;
if (indate.indexOf("-")!=-1) var sdate = indate.split("-");
else var sdate = indate.split("/");
if(type=="yy/mm/dd") {
var newdate = Array(3);
newdate[0] = sdate[1];
newdate[1] = sdate[2];
newdate[2] = sdate[0];
indate = newdate.join("/");
sdate = indate.split("/");
}
var chkDate=new Date(Date.parse(indate))
var cmpDate=(chkDate.getMonth()+1)+"/"+(chkDate.getDate())+"/"+(chkDate.getYear())
var indate2=(Math.abs(sdate[0]))+"/"+(Math.abs(sdate[1]))+"/"+(Math.abs(sdate[2]))
if (indate2!=cmpDate) rval = false;
else {
if (cmpDate=="NaN/NaN/NaN") rval = false;
else rval = true;
}
return rval;
}
//주민번호 확인
function is_ssn(SSN1, SSN2) {
if (SSN1.length != 6 || SSN2.length != 7) return false;
var SSN = SSN1 + SSN2;
var strA, strB, strC, strD, strE, strF, strG, strH, strI, strJ, strK, strL, strM, strN, strO;
var nCalA, nCalB, nCalC;
strA = SSN.substr(0, 1);
strB = SSN.substr(1, 1);
strC = SSN.substr(2, 1);
strD = SSN.substr(3, 1);
strE = SSN.substr(4, 1);
strF = SSN.substr(5, 1);
strG = SSN.substr(6, 1);
strH = SSN.substr(7, 1);
strI = SSN.substr(8, 1);
strJ = SSN.substr(9, 1);
strK = SSN.substr(10, 1);
strL = SSN.substr(11, 1);
strM = SSN.substr(12, 1);
// CheckSum
strO = strA*2 + strB*3 + strC*4 + strD*5 + strE*6 + strF*7 + strG*8 + strH*9 + strI*2 + strJ*3 + strK*4 + strL*5;
nCalA = eval(strO);
nCalB = nCalA % 11;
nCalC = 11 - nCalB;
nCalC = nCalC % 10;
strv = '19';
strw = SSN.substr(0, 2);
strx = SSN.substr(2, 2);
stry = SSN.substr(4, 2);
// 날짜수 체크
strz = strv + strw;
if ((strz % 4 == 0) && (strz % 100 != 0) || (strz % 400 == 0)) yunyear = 29;
else yunyear = 28;
if ((strx <= 0) || (strx > 12)) return false;
if ((strx == 1 || strx == 3 || strx == 5 || strx == 7 || strx == 8 || strx == 10 || strx == 12) && (stry > 31 || stry <= 0)) return false;
if ((strx == 4 || strx == 6 || strx == 9 || strx == 11) && (stry > 30 || stry <= 0)) return false;
if (strx == 2 && (stry > yunyear || stry <= 0)) return false;
if (!((strG == 1) || (strG == 2) || (strG == 3) || (strG ==4))) return false;
if ( nCalC != strM ) return false;
return true;
}
//입력시 값 체크 onkeydown="handlerNum()", 최대값 제한 : MaxLength="5"
function handlerNum()
{
e = window.event; //윈도우의 event를 잡는것입니다. 그냥 써주심됩니당.
//숫자열 0 ~ 9 : 48 ~ 57, 키패드 0 ~ 9 : 96 ~ 105 ,8 : backspace, 46 : delete -->키코드값을 구분합니다.
if(e.keyCode >= 48 && e.keyCode <= 57 || e.keyCode >= 96 && e.keyCode <= 105 || e.keyCode == 8 || e.keyCode == 46)
{ //delete나 backspace는 입력이 되어야되니까..
if(e.keyCode == 48 || e.keyCode == 96)//0을 눌렀을경우
{
if(txtBox1.value == "" ) //아무것도 없는상태에서 0을 눌렀을경우
e.returnValue=false; //-->입력되지않는다.
else //다른숫자뒤에오는 0은
return; //-->입력시킨다.
}
else //0이 아닌숫자
return; //-->입력시킨다.
}
else //숫자가 아니면 넣을수 없다.
{
alert('숫자만 입력가능합니다');
e.returnValue=false;
}
}
** 동적 쿼리르 사용하지 않고 스토어 프로시저와 파라미터를 사용
private void Get_List(int ID)
{
try
{
string ConnectStr = ConnectionString();
SqlConnection Conn = new SqlConnection(ConnectStr);
SqlDataAdapter da = new SqlDataAdapter();
SqlCommand Cmd = new SqlCommand();
Cmd.Connection = Conn;
Cmd.CommandText = "test_GetJobsID"; //스토어 프로시저사용
Cmd.CommandType = CommandType.StoredProcedure;
Cmd.Parameters.Add("@ID",SqlDbType.Int,4); //파라미터사용
Cmd.Parameters["@ID"].Value = ID; //입력값 체크한후 처리
da.SelectCommand= Cmd;
DataSet ds = new DataSet();
da.Fill(ds,"test");
DataGrid1.DataSource = ds.Tables[0].DefaultView;
DataGrid1.DataBind();
}
catch(SqlException ex)
{
//SQL에러시 처리
}
catch(Exception exp)
{
//에러처리
}
finally
{
//연결종결
}
}
//디비 연결스트링
public string ConnectionString()
{
Base64Code bc = new Base64Code();
return bc.Base64Decode(ConfigurationSettings.AppSettings["ConnectionString"]);
}
// Web.config 설정 추가
- 사용자의 모든 입력값에 대하여 불필요한 에러 메시지 숨김
//Web.config 파일 설정
mode="RemoteOnly" defaultRedirect="/error/errorinfo.aspx">
- 웹어플리케이션이 사용하는 데이타베이스 유저의 권한을 제한
2. MS-SQL상에서 시스템 명령어 실행
** 방식
- xp_cmdshell 을 이용한 시스템 명령실행.
- 기타명령어(xp_startmail, xp_sendmail, xp_dirtree, xp_regdeletekey,
xp_regenumvalues, xp_regread, xp_regwrite, sp_makewebtask, sp_adduser, ...)
** 웹페이지에 악성코드 삽입 방식 실제
; exec master..xp_cmdshell 'ping 10.10.1.2'--
; exec master..xp_cmdshell 'echo >> c:\inetpub\wwwroot\index.html';
** 명령을 삭제하여도 복구후 해킹할수 있으므로 수시확인 필요
num=119' user master dbcc addextendedproc('xp_cmdshell','xplog70.dll')
** 대책
DB의 권한 축소 및 불필요한 sp 제거 : db_owner 권한의 제거 및 일반 user권한 부여