구현단계 보안약점 기준 - 코드 삽입
유형 | 입력데이터 검증 및 표현 |
보안약점 | 코드 삽입 |
개요 | 공격자가 소프트웨어의 의도된 동작을 변경하도록 임의 코드를 삽입하여 소프트웨어가 비정상적으로 동작하도록 하는 보안약점을 말한다. 코드 삽입은 프로그래밍 언어 자체의 기능에 의해서만 제한된다는 점에서 운영체제 명령어 삽입과 다르다. 취약한 프로그램에서 사용자의 입력값에 코드가 포함되는 것을 허용할 경우, 공격자는 개발자가 의도하지 않은 코드를 실행하여 권한을 탈취하거나 인증 우회, 시스템 명령어 실행 등을 할 수 있다. |
보안대책 | 동적코드를 실행할 수 있는 함수를 사용하지 않는다. 필요 시, 실행 가능한 동적코드를 입력값으로 받지 않도록, 외부 입력값에 대하여 화이트리스트 방식으로 구현한다. 또는 유효한 문자만 포함하도록 동적코드에 사용되는 사용자 입력값을 필터링한다. |
진단방법 | 각 언어에서 제공하고 있는 동적실행 함수를 확인한다. 동적코드 실행에 사용되는 데이터가 신뢰할 수 있는 값인지 확인한다. 이 때, 데이터가 신뢰할 수 없는 값이나 별도의 검증절차가 없으면 취약하다고 판단한다. |
연관된 설계단계 기준 | 시스템 자원 접근 및 명령어 수행 입력값 검증 |
코드예제
● 안전하지 않은 코드 예 (Java)
public class CodeInjectionController {
@RequestMapping(value = "/execute", method = RequestMethod.GET)
public String execute(@RequestParam("src") String src) throws ScriptException {
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("javascript");
// 외부 입력값인 src를 javascript eval 함수로 실행하고 있어 안전하지 않다.
String retValue = (String) scriptEngine.eval(src);
return retValue;
}
}
조작된 인수를 입력한 공격코드를 이용하여 새로운 파일을 만들거나 덮어씌울 수 있다.
● 안전하지 않은 코드 예 (JSP)
<body>
<%
String name = request.getparameter("name");
%>
<script>
// 외부 입력값인 name을 javascript new Function()을 이용하여 문자열을 함수로 실행하고 있다.
(new Function(<%=name%>))();
</script>
</body>
javascript new Function()으로 동적으로 코드를 실행할 수 있다.
● 안전한 코드 예 (Java)
@RequestMapping(value = "/execute", method = RequestMethod.GET)
public String execute(@RequestParam("src") String src) throws ScriptException {
// 정규식을 이용하여 특수문자 입력시 예외를 발생시킨다.
if (src.matches("[\\w]*") == false) {
throw new IllegalArgumentException();
}
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine = scriptEngineManager.getEngineByName("javascript");
String retValue = (String)scriptEngine.eval(src);
return retValue;
}
외부 입력값을 필터링하여 사전 검증하는 코드를 추가한다.
● 안전한 코드 예 (Java)
@RequestMapping(value = "/execute", method = RequestMethod.GET)
public String execute(@RequestParam("src") String src) throws ScriptException {
// 유효한 문자 "_" 일 경우 실행할 메소드 호출한다.
if (src.matches("UNDER_BAR") == true) {
// 유효한 문자 "$" 일 경우 실행할 메소드 호출한다.
} else if (src.matches("DOLLAR") == true) {
// 유효하지 않은 특수문자 입력시 예외를 발생시킨다.
} else {
throw new IllegalArgumentException();
}
}
진단방법
각 언어에서 제공하고 있는 동적실행 함수를 확인한다.(①)
동적코드 실행에 사용되는 데이터가 신뢰할 수 있는 값인지 확인한다.(②)
이때, 데이터가 신뢰할 수 없는 값이나 별도의 검증절차가 없으면 취약하다고 판단한다.
● 일반적인 진단의 예
public class CodeInjection {
// eval 실행 데이터가 프로그램 실행 파라미터이다.
public static void main(String[] args) throws ScriptException { ·····················②
ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
ScriptEngine scriptEngine= scriptEngineManager.getEngineByName("javascript");
String src = "print('" + args[1] + "');";
scriptEngine.eval(src); ·························································①
}
}
● 정탐코드
class ACC {
public static void executeScript(final String name) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
// eval을 이용해서 변수 name 값을 표시한다.
engine.eval("print('" + name + "')");
}
}
...
@RequestMapping(value = "/print", method = RequestMethod.POST)
public void print(@RequestParam("name") String name) throws ScriptException{
// 사용자 입력값인 name을 이용해서 eval 을 사용하는 함수를 호출한다.
ACC.executeScript(name);
...
}
동적코드 실행 함수인 ScriptEngine.eval함수를 사용하고 있으며, 파라미터 데이터 name은 사용자의 입력 값이므로 취약하다고 판정한다.
● 정탐코드
vul.php
$myvar = 'somevalue';
$x = $_GET['arg'];
eval('$myvar = ' . $x . ';');
위 코드는 공격자가 arg 파라미터에 아래와 같은 명령어를 추가하여 PHP 삽입코드를 실행될 수 있으나 이를 사전에 검증하지 않으면 취약한 것으로 판단한다.
[ 삽입코드의 예 ]
/vul.php?arg=1;phpinfo()
/vul.php?arg=1;system("uname -a")
/vul.php?arg=1;system("cat /etc/passwd")
/vul.php?arg=1;system("ps -ef")
※ PHP의 eval() 함수는 PHP 코드를 실행하는 함수이며, 괄호 안의 문자열을 실행한다.
● 정탐코드
$MessageFile = "cwe-94/messages.out";
if ($_GET["action"] == "NewMessage") {
$name = $_GET["name"];
$message = $_GET["message"];
$handle = fopen($MessageFile, "a+");
fwrite($handle, "<b>$name</b> says '$message'<hr>\n");
fclose($handle);
echo "Message Saved!<p>\n";
} else if ($_GET["action"] == "ViewMessages") {
include($MessageFile);
}
위 예에서는 사용자 메시지를 파일에 쓰고 사용자가 볼 수 있도록 한다. 이 때, 공격자는 name과 message 파라미터에 악의적인 메시지를 입력하면 필터링 과정 없이 파일에 저장된다. PHP가 파싱하고 실행하는 과정에서 메시지 조회 기능을 수행하게 될 경우 메시지 출력 시 저장된 파일을 보여주게 되어 의도하지 않은 PHP 명령어가 실행될 수 있으므로 취약하다고 판단한다.
● 오탐코드
$myvar = "varname";
$x = $_GET['arg'];
$x=preg_replace("/[w\\^a-z0-9]/i", "", $x);
eval("\$myvar = \$x;");
대부분의 경우 애플리케이션을 보호하기 위해 잠재적으로 위험한 모든 기호를 제거하면 문제 발생을 줄일 수 있다. arg 파라미터가 eval() 함수에서 사용되기 전에 위 예제와 같이 잠재적으로 위험한 기호를 제거하기 때문에 취약하지 않다고 판정한다.
● 오탐코드
class ACC {
public static void executeScript(final String name) throws ScriptException {
if (!Filter.filterScript(name)) {
throw new IllegalArgumentException();
}
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
engine.eval("print('" + name + "')");
...
}
...
@Controller
public class evalPrint {
@RequestMapping(value = "/print", method = RequestMethod.POST)
public Student upload(String name) throws ClassNotFoundException, IOException {
ACC.executeScript(name);
...
public class Filter {
public static boolean filterScript(String name) {
boolean returnValue = false;
// 알파벳, 숫자, '_' 만을 허용한다.
if (name.matches("[\\w]*")) {
returnValue = true;
}
return returnValue;
}
}
외부로부터 입력 값을 받아 사용하기 전에 검증을 수행하기 때문에 취약하지 않다고 판정한다.
● 오탐코드
<script>
var obj = {};
for(var i = 0; i < 5; i++){
eval("obj.test" + i + "=" + i);
}
console.log(obj);
</script>
동적 실행함수를 사용하였으나 외부 입력값을 실행하지 않아 취약하지 않다고 판정한다.