입력 검증 및 표현 문제는 메타 문자, 대체 인코딩 및 숫자 표현 때문에 발생합니다. 보안 문제는 입력을 신뢰하기 때문에 발생합니다. 문제로는 "Buffer Overflows", "Cross-Site Scripting" 공격, "SQL Injection", 그 외 여러 가지가 있습니다.
...
DirectorySearcher src =
new DirectorySearcher("(manager=" + managerName.Text + ")");
src.SearchRoot = de;
src.SearchScope = SearchScope.Subtree;
foreach(SearchResult res in src.FindAll()) {
...
}
(manager=Smith, John)
managerName
에 LDAP 메타 문자가 들어 있지 않은 경우에만 정확하게 동작합니다. 공격자가 managerName
에 Hacker, Wiley)(|(objectclass=*)
문자열을 입력하면 쿼리는 다음과 같습니다.
(manager=Hacker, Wiley)(|(objectclass=*))
|(objectclass=*)
조건을 추가하면 필터가 디렉터리의 모든 항목과 일치되며 공격자가 전체 사용자 풀에 대한 정보를 검색할 수 있습니다. LDAP 쿼리를 수행하는 권한에 따라 이 공격의 범위가 제한될 수도 있지만, 공격자가 쿼리의 명령 구조를 제어할 수 있을 경우, 그러한 공격은 LDAP 쿼리를 실행하는 사용자가 접근할 수 있는 모든 레코드에 영향을 줄 수 있습니다.
fgets(manager, sizeof(manager), socket);
snprintf(filter, sizeof(filter, "(manager=%s)", manager);
if ( ( rc = ldap_search_ext_s( ld, FIND_DN, LDAP_SCOPE_BASE,
filter, NULL, 0, NULL, NULL, LDAP_NO_LIMIT,
LDAP_NO_LIMIT, &result ) ) == LDAP_SUCCESS ) {
...
}
(manager=Smith, John)
manager
에 LDAP 메타 문자가 들어 있지 않은 경우에만 정확하게 동작합니다. 공격자가 manager
에 Hacker, Wiley)(|(objectclass=*)
문자열을 입력하면 쿼리는 다음과 같습니다.
(manager=Hacker, Wiley)(|(objectclass=*))
|(objectclass=*)
조건을 추가하면 필터가 디렉터리의 모든 항목과 일치되며 공격자가 전체 사용자 풀에 대한 정보를 검색할 수 있습니다. LDAP 쿼리를 수행하는 권한에 따라 이 공격의 범위가 제한될 수도 있지만, 공격자가 쿼리의 명령 구조를 제어할 수 있을 경우, 그러한 공격은 LDAP 쿼리를 실행하는 사용자가 접근할 수 있는 모든 레코드에 영향을 줄 수 있습니다.
...
DirContext ctx = new InitialDirContext(env);
String managerName = request.getParameter("managerName");
//retrieve all of the employees who report to a manager
String filter = "(manager=" + managerName + ")";
NamingEnumeration employees = ctx.search("ou=People,dc=example,dc=com",
filter);
...
(manager=Smith, John)
managerName
에 LDAP 메타 문자가 들어 있지 않은 경우에만 정확하게 동작합니다. 공격자가 managerName
에 Hacker, Wiley)(|(objectclass=*)
문자열을 입력하면 쿼리는 다음과 같습니다.
(manager=Hacker, Wiley)(|(objectclass=*))
|(objectclass=*)
조건을 추가하면 필터가 디렉터리의 모든 항목과 일치되며 공격자가 전체 사용자 풀에 대한 정보를 검색할 수 있습니다. LDAP 쿼리를 수행하는 권한에 따라 이 공격의 범위가 제한될 수도 있지만, 공격자가 쿼리의 명령 구조를 제어할 수 있을 경우, 그러한 공격은 LDAP 쿼리를 실행하는 사용자가 접근할 수 있는 모든 레코드에 영향을 줄 수 있습니다.
...
$managerName = $_POST["managerName"]];
//retrieve all of the employees who report to a manager
$filter = "(manager=" . $managerName . ")";
$result = ldap_search($ds, "ou=People,dc=example,dc=com", $filter);
...
(manager=Smith, John)
managerName
에 LDAP 메타 문자가 들어 있지 않은 경우에만 정확하게 동작합니다. 공격자가 managerName
에 Hacker, Wiley)(|(objectclass=*)
문자열을 입력하면 쿼리는 다음과 같습니다.
(manager=Hacker, Wiley)(|(objectclass=*))
|(objectclass=*)
조건을 추가하면 필터가 디렉터리의 모든 항목과 일치되며 공격자가 전체 사용자 풀에 대한 정보를 검색할 수 있습니다. LDAP 쿼리를 수행하는 권한에 따라 이 공격의 범위가 제한될 수도 있지만, 공격자가 쿼리의 명령 구조를 제어할 수 있을 경우, 그러한 공격은 LDAP 쿼리를 실행하는 사용자가 액세스할 수 있는 모든 레코드에 영향을 줄 수 있습니다.ou
문자열을 읽고 이를 사용하여 새로운 DirectoryEntry
를 생성합니다.
...
de = new DirectoryEntry("LDAP://ad.example.com:389/ou="
+ hiddenOU.Text + ",dc=example,dc=com");
...
ou
값을 지정하여 쿼리 결과를 수정할 수 있습니다. 문제는 개발자가 이후의 쿼리를 제한하여 현재 사용자에게 읽기가 허용된 직원 레코드에만 접근하는 데 필수적인 적합한 액세스 제어 메커니즘을 이용하지 못했다는 점입니다.dn
문자열을 읽어 이를 사용하여 LDAP 쿼리를 수행합니다.
...
rc = ldap_simple_bind_s( ld, NULL, NULL );
if ( rc != LDAP_SUCCESS ) {
...
}
...
fgets(dn, sizeof(dn), socket);
if ( ( rc = ldap_search_ext_s( ld, dn, LDAP_SCOPE_BASE,
filter, NULL, 0, NULL, NULL, LDAP_NO_LIMIT,
LDAP_NO_LIMIT, &result ) ) != LDAP_SUCCESS ) {
...
dn
문자열을 지정하여 쿼리 결과를 수정할 수 있습니다. 문제는 개발자가 이후의 쿼리를 제한하여 현재 사용자에게 읽기가 허용된 직원 레코드에만 접근하는 데 필수적인 적합한 액세스 제어 메커니즘을 이용하지 못했다는 점입니다.
env.put(Context.SECURITY_AUTHENTICATION, "none");
DirContext ctx = new InitialDirContext(env);
String empID = request.getParameter("empID");
try
{
BasicAttribute attr = new BasicAttribute("empID", empID);
NamingEnumeration employee =
ctx.search("ou=People,dc=example,dc=com",attr);
...
dn
문자열을 읽어 이를 사용하여 LDAP 쿼리를 수행합니다.
$dn = $_POST['dn'];
if (ldap_bind($ds)) {
...
try {
$rs = ldap_search($ds, $dn, "ou=People,dc=example,dc=com", $attr);
...
dn
이 사용자 입력에서 오고 쿼리가 익명의 바인딩에서 수행되므로 공격자는 예상치 못한 dn 문자열을 지정하여 쿼리 결과를 수정할 수 있습니다. 문제는 개발자가 이후의 쿼리를 제한하여 현재 사용자에게 읽기가 허용된 직원 레코드에만 접근하는 데 필수적인 적합한 액세스 제어 메커니즘을 이용하지 못했다는 점입니다.webview
로 페이지를 로드하지만 악성 사이트 방문 시 자동 다이얼링 공격을 방지하는 어떠한 제어도 구현하지 않습니다.webview
를 사용하여 신뢰할 수 없는 링크가 포함되어 있을 수 있는 사이트를 로드하지만 이 webview
에서 시작된 요청을 검증할 수 있는 대리자를 지정하지 않습니다.
...
NSURL *webUrl = [[NSURL alloc] initWithString:@"https://some.site.com/"];
NSURLRequest *webRequest = [[NSURLRequest alloc] initWithURL:webUrl];
[_webView loadRequest:webRequest];
...
webview
로 페이지를 로드하지만 악성 사이트 방문 시 자동 다이얼링 공격을 방지하는 어떠한 제어도 구현하지 않습니다.webview
를 사용하여 신뢰할 수 없는 링크가 포함되어 있을 수 있는 사이트를 로드하지만 이 webview
에서 시작된 요청을 검증할 수 있는 대리자를 지정하지 않습니다.
...
let webUrl : NSURL = NSURL(string: "https://some.site.com/")!
let webRequest : NSURLRequest = NSURLRequest(URL: webUrl)
webView.loadRequest(webRequest)
...
webview
로 페이지를 로드하지만 사용자가 클릭할 수 있는 링크를 확인 또는 검증하는 제어를 구현하지 않습니다.webview
를 사용하여 신뢰할 수 없는 링크가 포함되어 있을 수 있는 사이트를 로드하지만 이 webview
에서 시작된 요청을 검증할 수 있는 대리자를 지정하지 않습니다.
...
NSURL *webUrl = [[NSURL alloc] initWithString:@"https://some.site.com/"];
NSURLRequest *webRequest = [[NSURLRequest alloc] initWithURL:webUrl];
[webView loadRequest: webRequest];
webview
로 페이지를 로드하지만 사용자가 클릭할 수 있는 링크를 확인 또는 검증하는 제어를 구현하지 않습니다.webview
를 사용하여 신뢰할 수 없는 링크가 포함되어 있을 수 있는 사이트를 로드하지만 이 webview
에서 시작된 요청을 검증할 수 있는 대리자를 지정하지 않습니다.
...
let webUrl = URL(string: "https://some.site.com/")!
let urlRequest = URLRequest(url: webUrl)
webView.load(webRequest)
...
...
DATA log_msg TYPE bal_s_msg.
val = request->get_form_field( 'val' ).
log_msg-msgid = 'XY'.
log_msg-msgty = 'E'.
log_msg-msgno = '123'.
log_msg-msgv1 = 'VAL: '.
log_msg-msgv2 = val.
CALL FUNCTION 'BAL_LOG_MSG_ADD'
EXPORTING
I_S_MSG = log_msg
EXCEPTIONS
LOG_NOT_FOUND = 1
MSG_INCONSISTENT = 2
LOG_IS_FULL = 3
OTHERS = 4.
...
val
에 문자열 "FOO
"을 전송하면 다음 항목이 기록됩니다.
XY E 123 VAL: FOO
FOO XY E 124 VAL: BAR
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
XY E 123 VAL: FOO XY E 124 VAL: BAR
var params:Object = LoaderInfo(this.root.loaderInfo).parameters;
var val:String = String(params["username"]);
var value:Number = parseInt(val);
if (value == Number.NaN) {
trace("Failed to parse val = " + val);
}
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
Failed to parse val=twenty-one
User logged out=badguy
...
string val = (string)Session["val"];
try {
int value = Int32.Parse(val);
}
catch (FormatException fe) {
log.Info("Failed to parse val= " + val);
}
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
long value = strtol(val, &endPtr, 10);
if (*endPtr != '\0')
syslog(LOG_INFO,"Illegal value = %s",val);
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
Illegal value=twenty-one
twenty-one\n\nINFO: User logged out=evil
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Illegal value=twenty-one
INFO: User logged out=evil
...
01 LOGAREA.
05 VALHEADER PIC X(50) VALUE 'VAL: '.
05 VAL PIC X(50).
...
EXEC CICS
WEB READ
FORMFIELD(NAME)
VALUE(VAL)
...
END-EXEC.
EXEC DLI
LOG
FROM(LOGAREA)
LENGTH(50)
END-EXEC.
...
VAL
에 문자열 "FOO
"을 전송하면 다음 항목이 기록됩니다.
VAL: FOO
FOO VAL: BAR
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
VAL: FOO VAL: BAR
<cflog file="app_log" application="No" Thread="No"
text="Failed to parse val="#Form.val#">
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
"Information",,"02/28/01","14:50:37",,"Failed to parse val=twenty-one"
twenty-one%0a%0a%22Information%22%2C%2C%2202/28/01%22%2C%2214:53:40%22%2C%2C%22User%20logged%20out:%20badguy%22
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
"Information",,"02/28/01","14:50:37",,"Failed to parse val=twenty-one"
"Information",,"02/28/01","14:53:40",,"User logged out: badguy"
func someHandler(w http.ResponseWriter, r *http.Request){
r.parseForm()
name := r.FormValue("name")
logout := r.FormValue("logout")
...
if (logout){
...
} else {
log.Printf("Attempt to log out: name: %s logout: %s", name, logout)
}
}
logout
에 대해 "twenty-one
" 문자열을 제출하고 "admin
"이라는 이름을 가진 사용자를 생성할 수 있는 경우 다음 항목이 기록됩니다.
Attempt to log out: name: admin logout: twenty-one
admin+logout:+1+++++++++++++++++++++++
"라는 사용자 이름을 생성할 수 있으면 다음 항목이 기록됩니다.
Attempt to log out: name: admin logout: 1 logout: twenty-one
...
String val = request.getParameter("val");
try {
int value = Integer.parseInt(val);
}
catch (NumberFormatException nfe) {
log.info("Failed to parse val = " + val);
}
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
Example 1
을 Android 플랫폼에 맞게 조정합니다.
...
String val = this.getIntent().getExtras().getString("val");
try {
int value = Integer.parseInt();
}
catch (NumberFormatException nfe) {
Log.e(TAG, "Failed to parse val = " + val);
}
...
var cp = require('child_process');
var http = require('http');
var url = require('url');
function listener(request, response){
var val = url.parse(request.url, true)['query']['val'];
if (isNaN(val)){
console.log("INFO: Failed to parse val = " + val);
}
...
}
...
http.createServer(listener).listen(8080);
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Failed to parse val = twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
long value = strtol(val, &endPtr, 10);
if (*endPtr != '\0')
NSLog("Illegal value = %s",val);
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Illegal value=twenty-one
twenty-one\n\nINFO: User logged out=evil
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Illegal value=twenty-one
INFO: User logged out=evil
<?php
$name =$_GET['name'];
...
$logout =$_GET['logout'];
if(is_numeric($logout))
{
...
}
else
{
trigger_error("Attempt to log out: name: $name logout: $val");
}
?>
logout
에 대해 "twenty-one
" 문자열을 제출하고 "admin
"이라는 이름을 가진 사용자를 생성할 수 있는 경우 다음 항목이 기록됩니다.
PHP Notice: Attempt to log out: name: admin logout: twenty-one
admin+logout:+1+++++++++++++++++++++++
"라는 사용자 이름을 생성할 수 있으면 다음 항목이 기록됩니다.
PHP Notice: Attempt to log out: name: admin logout: 1 logout: twenty-one
name = req.field('name')
...
logout = req.field('logout')
if (logout):
...
else:
logger.error("Attempt to log out: name: %s logout: %s" % (name,logout))
logout
에 대해 "twenty-one
" 문자열을 제출하고 "admin
"이라는 이름을 가진 사용자를 생성할 수 있는 경우 다음 항목이 기록됩니다.
Attempt to log out: name: admin logout: twenty-one
admin+logout:+1+++++++++++++++++++++++
"라는 사용자 이름을 생성할 수 있으면 다음 항목이 기록됩니다.
Attempt to log out: name: admin logout: 1 logout: twenty-one
...
val = req['val']
unless val.respond_to?(:to_int)
logger.info("Failed to parse val")
logger.info(val)
end
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Failed to parse val
INFO: twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Failed to parse val
INFO: twenty-one
INFO: User logged out=badguy
...
let num = Int(param)
if num == nil {
NSLog("Illegal value = %@", param)
}
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Illegal value = twenty-one
twenty-one\n\nINFO: User logged out=evil
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Illegal value=twenty-one
INFO: User logged out=evil
...
Dim Val As Variant
Dim Value As Integer
Set Val = Request.Form("val")
If IsNumeric(Val) Then
Set Value = Val
Else
App.EventLog "Failed to parse val=" & Val, 1
End If
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
Failed to parse val=twenty-one
twenty-one%0a%0a+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
Failed to parse val=twenty-one
User logged out=badguy
@HttpGet
global static void doGet() {
RestRequest req = RestContext.request;
String val = req.params.get('val');
try {
Integer i = Integer.valueOf(val);
...
} catch (TypeException e) {
System.Debug(LoggingLevel.INFO, 'Failed to parse val: '+val);
}
}
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
Failed to parse val: twenty-one
twenty-one%0a%0aUser+logged+out%3dbadguy
" 문자열을 다음 항목이 기록됩니다.
Failed to parse val: twenty-one
User logged out=badguy
...
String val = request.Params["val"];
try {
int value = Int.Parse(val);
}
catch (FormatException fe) {
log.Info("Failed to parse val = " + val);
}
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
Example 1
을 Android 플랫폼에 맞게 조정합니다.
...
String val = this.Intent.Extras.GetString("val");
try {
int value = Int.Parse(val);
}
catch (FormatException fe) {
Log.E(TAG, "Failed to parse val = " + val);
}
...
...
var idValue string
idValue = req.URL.Query().Get("id")
num, err := strconv.Atoi(idValue)
if err != nil {
sysLog.Debug("Failed to parse value: " + idValue)
}
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
...
String val = request.getParameter("val");
try {
int value = Integer.parseInt(val);
}
catch (NumberFormatException nfe) {
log.info("Failed to parse val = " + val);
}
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
Example 1
을 Android 플랫폼에 맞게 조정합니다.
...
String val = this.getIntent().getExtras().getString("val");
try {
int value = Integer.parseInt();
}
catch (NumberFormatException nfe) {
Log.e(TAG, "Failed to parse val = " + val);
}
...
var cp = require('child_process');
var http = require('http');
var url = require('url');
function listener(request, response){
var val = url.parse(request.url, true)['query']['val'];
if (isNaN(val)){
console.error("INFO: Failed to parse val = " + val);
}
...
}
...
http.createServer(listener).listen(8080);
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
...
val = request.GET["val"]
try:
int_value = int(val)
except:
logger.debug("Failed to parse val = " + val)
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
twenty-one%0a%0aINFO:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
INFO: Failed to parse val=twenty-one
INFO: User logged out=badguy
...
val = req['val']
unless val.respond_to?(:to_int)
logger.debug("Failed to parse val")
logger.debug(val)
end
...
val
에 문자열 "twenty-one
"을 전송하면 다음 항목이 기록됩니다.
DEBUG: Failed to parse val
DEBUG: twenty-one
twenty-one%0a%DEBUG:+User+logged+out%3dbadguy
" 문자열을 전송하는 경우 다음 항목이 기록됩니다.
DEBUG: Failed to parse val
DEBUG: twenty-one
DEBUG: User logged out=badguy
CREATE
명령을 만듭니다. 공격자는 이 매개 변수를 사용하여 서버로 전송되는 명령을 수정하고 CRLF 문자를 사용하여 새 명령을 삽입할 수 있습니다.
...
final String foldername = request.getParameter("folder");
IMAPFolder folder = (IMAPFolder) store.getFolder("INBOX");
...
folder.doCommand(new IMAPFolder.ProtocolCommand() {
@Override
public Object doCommand(IMAPProtocol imapProtocol) throws ProtocolException {
try {
imapProtocol.simpleCommand("CREATE " + foldername, null);
} catch (Exception e) {
// Handle Exception
}
return null;
}
});
...
USER
및 PASS
명령을 만듭니다. 공격자는 이 매개 변수를 사용하여 서버로 전송되는 명령을 수정하고 CRLF 문자를 사용하여 새 명령을 삽입할 수 있습니다.
...
String username = request.getParameter("username");
String password = request.getParameter("password");
...
POP3SClient pop3 = new POP3SClient(proto, false);
pop3.login(username, password)
...
VRFY
명령을 작성합니다. 공격자는 이 매개 변수를 사용하여 서버로 전송된 명령을 수정하고 CRLF 문자를 사용하는 새 명령을 주입할 수 있습니다.
...
c, err := smtp.Dial(x)
if err != nil {
log.Fatal(err)
}
user := request.FormValue("USER")
c.Verify(user)
...
VRFY
명령을 만듭니다. 공격자는 이 매개 변수를 사용하여 서버로 전송되는 명령을 수정하고 CRLF 문자를 사용하여 새 명령을 삽입할 수 있습니다.
...
String user = request.getParameter("user");
SMTPSSLTransport transport = new SMTPSSLTransport(session,new URLName(Utilities.getProperty("smtp.server")));
transport.connect(Utilities.getProperty("smtp.server"), username, password);
transport.simpleCommand("VRFY " + user);
...
VRFY
명령을 만듭니다. 공격자는 이 매개 변수를 사용하여 서버로 전송되는 명령을 수정하고 CRLF 문자를 사용하여 새 명령을 삽입할 수 있습니다.
...
user = request.GET['user']
session = smtplib.SMTP(smtp_server, smtp_tls_port)
session.ehlo()
session.starttls()
session.login(username, password)
session.docmd("VRFY", user)
...
...
TextClient tc = (TextClient)Client.GetInstance("127.0.0.1", 11211, MemcachedFlags.TextProtocol);
tc.Open();
string id = txtID.Text;
var result = get_page_from_somewhere();
var response = Http_Response(result);
tc.Set("req-" + id, response, TimeSpan.FromSeconds(1000));
tc.Close();
tc = null;
...
set req-1233 0 1000 n
<serialized_response_instance>
n
은 응답의 길이입니다.ignore 0 0 1\r\n1\r\nset injected 0 3600 10\r\n0123456789\r\nset req-
를 보낼 수 있으며, 그러면 작업이 다음과 같이 됩니다.
set req-ignore 0 0 1
1
set injected 0 3600 10
0123456789
set req-1233 0 0 n
<serialized_response_instance>
injected=0123456789
캐시에 새 키/값 쌍을 추가하며, 공격자가 이 캐시를 감염시킬 수 있습니다.
...
def store(request):
id = request.GET['id']
result = get_page_from_somewhere()
response = HttpResponse(result)
cache_time = 1800
cache.set("req-" % id, response, cache_time)
return response
...
set req-1233 0 0 n
<serialized_response_instance>
ignore 0 0 1\r\n1\r\nset injected 0 3600 10\r\n0123456789\r\nset req-
를 보낼 수 있으며, 그러면 작업이 다음과 같이 됩니다.
set req-ignore 0 0 1
1
set injected 0 3600 10
0123456789
set req-1233 0 0 n
<serialized_response_instance>
injected=0123456789
캐시에 새 키/값 쌍을 성공적으로 추가합니다. 페이로드에 따라 다르지만 공격자는 역직렬화 시 임의의 코드를 실행하며 Pickle을 통해 직렬화된 페이로드를 주입하여 임의의 코드를 실행하거나 캐시를 감염시킬 수 있습니다.
def form = Form(
mapping(
"name" -> text,
"age" -> number
)(UserData.apply)(UserData.unapply)
)
FormAction
을 정의합니다.예제 2: 다음 코드는 예상된 요구 사항을 기준으로 데이터를 검증하지 않는 Spring WebFlow 작업 상태를 정의합니다.
<bean id="customerCriteriaAction" class="org.springframework.webflow.action.FormAction">
<property name="formObjectClass"
value="com.acme.domain.CustomerCriteria" />
<property name="propertyEditorRegistrar">
<bean
class="com.acme.web.PropertyEditors" />
</property>
</bean>
<action-state>
<action bean="transferMoneyAction" method="bind" />
</action-state>
def form = Form(
mapping(
"name" -> text,
"age" -> number
)(UserData.apply)(UserData.unapply)
)
...
String userName = User.Identity.Name;
String emailId = request["emailId"];
var client = account.CreateCloudTableClient();
var table = client.GetTableReference("Employee");
var query = table.CreateQuery<EmployeeEntity>().Where("user == '" + userName + "' AND emailId == '" + emailId "'");
var results = table.ExecuteQuery(query);
...
user == "<userName>" && emailId == "<emailId>"
emailId
에 작은따옴표가 들어 있지 않은 경우에만 정확하게 동작합니다. 사용자 이름이 wiley
인 공격자가 emailId
에 문자열 123' || '4' != '5
를 입력하면 쿼리는 다음과 같이 생성됩니다.
user == 'wiley' && emailId == '123' || '4' != '5'
|| '4' != '5'
조건을 추가하면 where 절이 항상 true
로 평가되므로 쿼리는 전자 메일 소유자와 관계없이 emails
컬렉션에 저장된 모든 항목을 반환합니다.
...
// "type" parameter expected to be either: "Email" or "Username"
string type = request["type"];
string value = request["value"];
string password = request["password"];
var ddb = new AmazonDynamoDBClient();
var attrValues = new Dictionary<string,AttributeValue>();
attrValues[":value"] = new AttributeValue(value);
attrValues[":password"] = new AttributeValue(password);
var scanRequest = new ScanRequest();
scanRequest.FilterExpression = type + " = :value AND Password = :password";
scanRequest.TableName = "users";
scanRequest.ExpressionAttributeValues = attrValues;
var scanResponse = await ddb.ScanAsync(scanRequest);
...
Email = :value AND Password = :password
Username = :value AND Password = :password
type
에 예상 값만 포함된 경우에만 정확하게 동작합니다. 공격자가 :value = :value OR :value
같은 입력 값을 제공하면 쿼리는 다음과 같습니다.:value = :value OR :value = :value AND Password = :password
:value = :value
조건을 추가하면 where 절이 항상 true로 평가되므로 쿼리는 전자 메일 소유자와 관계없이 users
컬렉션에 저장된 모든 항목을 반환합니다.
...
// "type" parameter expected to be either: "Email" or "Username"
String type = request.getParameter("type")
String value = request.getParameter("value")
String password = request.getParameter("password")
DynamoDbClient ddb = DynamoDbClient.create();
HashMap<String, AttributeValue> attrValues = new HashMap<String,AttributeValue>();
attrValues.put(":value", AttributeValue.builder().s(value).build());
attrValues.put(":password", AttributeValue.builder().s(password).build());
ScanRequest queryReq = ScanRequest.builder()
.filterExpression(type + " = :value AND Password = :password")
.tableName("users")
.expressionAttributeValues(attrValues)
.build();
ScanResponse response = ddb.scan(queryReq);
...
Email = :value AND Password = :password
Username = :value AND Password = :password
type
에 예상 값만 포함된 경우에만 정확하게 동작합니다. 공격자가 :value = :value OR :value
같은 입력 값을 제공하면 쿼리는 다음과 같습니다.:value = :value OR :value = :value AND Password = :password
:value = :value
조건을 추가하면 where 절이 항상 true로 평가되므로 쿼리는 전자 메일 소유자와 관계없이 users
컬렉션에 저장된 모든 항목을 반환합니다.
...
function getItemsByOwner(username: string) {
db.items.find({ $where: `this.owner === '${username}'` }).then((orders: any) => {
console.log(orders);
}).catch((err: any) => {
console.error(err);
});
}
...
db.items.find({ $where: `this.owner === 'john'; return true; //` })
...
String userName = User.Identity.Name;
String emailId = request["emailId"];
var coll = mongoClient.GetDatabase("MyDB").GetCollection<BsonDocument>("emails");
var docs = coll.Find(new BsonDocument("$where", "this.name == '" + name + "'")).ToList();
...
this.owner == "<userName>" && this.emailId == "<emailId>"
emailId
에 작은따옴표가 들어 있지 않은 경우에만 정확하게 동작합니다. 사용자 이름이 wiley
인 공격자가 emailId
에 문자열 123' || '4' != '5
를 입력하면 쿼리는 다음과 같이 생성됩니다.
this.owner == 'wiley' && this.emailId == '123' || '4' != '5'
|| '4' != '5'
조건을 추가하면 where 절이 항상 true
로 평가되므로 쿼리는 전자 메일 소유자와 관계없이 emails
컬렉션에 저장된 모든 항목을 반환합니다.
...
String userName = ctx.getAuthenticatedUserName();
String emailId = request.getParameter("emailId")
MongoCollection<Document> col = mongoClient.getDatabase("MyDB").getCollection("emails");
BasicDBObject Query = new BasicDBObject();
Query.put("$where", "this.owner == \"" + userName + "\" && this.emailId == \"" + emailId + "\"");
FindIterable<Document> find= col.find(Query);
...
this.owner == "<userName>" && this.emailId == "<emailId>"
emailId
에 큰따옴표가 들어 있지 않은 경우에만 정확하게 동작합니다. 사용자 이름이 wiley
인 공격자가 emailId
에 문자열 123" || "4" != "5
를 입력하면 쿼리는 다음과 같이 생성됩니다.
this.owner == "wiley" && this.emailId == "123" || "4" != "5"
|| "4" != "5"
조건을 추가하면 where 절이 항상 true로 평가되므로 쿼리는 전자 메일 소유자와 관계없이 emails
컬렉션에 저장된 모든 항목을 반환합니다.
...
userName = req.field('userName')
emailId = req.field('emaiId')
results = db.emails.find({"$where", "this.owner == \"" + userName + "\" && this.emailId == \"" + emailId + "\""});
...
this.owner == "<userName>" && this.emailId == "<emailId>"
emailId
에 큰따옴표가 들어 있지 않은 경우에만 정확하게 동작합니다. 사용자 이름이 wiley
인 공격자가 emailId
에 문자열 123" || "4" != "5
를 입력하면 쿼리는 다음과 같이 생성됩니다.
this.owner == "wiley" && this.emailId == "123" || "4" != "5"
|| "4" != "5"
조건을 추가하면 where
절이 항상 true로 평가되므로 쿼리는 전자 메일 소유자와 관계없이 emails
컬렉션에 저장된 모든 항목을 반환합니다.
...
NSString *emailId = [self getEmailIdFromUser];
NSString *query = [NSString stringWithFormat:@"id == '%@'", emailId];
RLMResults<Email *> *emails = [Email objectsInRealm:realm where:query];
...
id == '<emailId value>'
emailId
에 작은따옴표가 들어 있지 않은 경우에만 정확하게 동작합니다. 공격자가 123' or '4' != '5
에 emailId
문자열을 입력하면 쿼리는 다음과 같습니다.
id == '123' or '4' != '5'
or '4' != '5'
조건을 추가하면 where
절이 항상 true로 평가되기 때문에, 쿼리는 전자 메일 소유자와 관계없이 emails
컬렉션에 저장된 모든 항목을 반환합니다.
...
let emailId = getFromUser("emailId")
let email = realm.objects(Email.self).filter("id == '" + emailId + "'")
...
id == '<emailId value>'
emailId
에 작은따옴표가 들어 있지 않은 경우에만 정확하게 동작합니다. 공격자가 123' or '4' != '5
에 emailId
문자열을 입력하면 쿼리는 다음과 같습니다.
id == '123' or '4' != '5'
or '4' != '5'
조건을 추가하면 filter
절이 항상 true로 평가되기 때문에, 쿼리는 전자 메일 소유자와 관계없이 emails
컬렉션에 저장된 모든 항목을 반환합니다.unserialize()
함수로 전달되기 전에 신뢰할 수 없는 데이터가 올바르게 정화되지 않는 경우 발생합니다. 공격자는 취약한 unserialize()
호출에 특히 수동으로 직렬화한 문자열을 전달할 수 있어 응용 프로그램 영역에서 임의 PHP object injection을 야기합니다. 이 취약점의 심각도는 응용 프로그램 영역에서 사용 가능한 클래스에 따라 달라집니다. __wakeup
또는 __destruct
같은 PHP 매직 메서드를 구현하는 클래스는 이러한 메서드 내에서 코드를 수행할 수 있으므로 공격자가 관심을 갖기 쉽습니다.__destruct()
매직 메서드를 구현하고 클래스 속성으로 정의된 시스템 명령을 실행하는 PHP 클래스를 보여줍니다. 또한 사용자 공급 데이터가 포함된 unserialize()
에 대한 불안정한 호출도 있습니다.
...
class SomeAvailableClass {
public $command=null;
public function __destruct() {
system($this->command);
}
}
...
$user = unserialize($_GET['user']);
...
Example 1
에서 응용 프로그램은 직렬화된 User
개체를 예상하지만 공격자는 command
속성을 위해 사전 정의된 값으로 SomeAvailableClass
의 직렬화된 버전을 실제로 제공할 수 있습니다.
GET REQUEST: http://server/page.php?user=O:18:"SomeAvailableClass":1:{s:7:"command";s:8:"uname -a";}
$user
개체에 대한 다른 참조가 없을 때 즉시 호출되며 공격자가 제공한 명령을 실행합니다.unserialize()
가 "속성 지향 프로그래밍"이라는 기술을 사용하여 호출된 경우 선언된 다른 클래스를 연결할 수 있습니다. "속성 지향 프로그래밍"은 BlackHat 2010 컨퍼런스에서 Stefan Esser가 소개한 기술입니다. 이 기술을 통해 공격자는 고유한 페이로드를 만들도록 기존의 코드를 재사용할 수 있습니다.YAML.load()
같은 데이터를 역직렬화하는 함수로 전달되기 전에 신뢰할 수 없는 데이터가 올바르게 정화되지 않는 경우에 발생합니다. 공격자가 특수하게 만들어진 직렬화된 문자열을 취약한 YAML.load()
호출에 전달하면 역직렬화 시 클래스가 응용 프로그램으로 로드되는 경우 임의의 Ruby 개체가 프로그램에 주입됩니다. 이렇게 되면 cross-site scripting 취약점을 찾기 위한 검증 로직을 우회하는 등의 다양한 공격 기회에 무방비 상태로 노출될 수 있으며, 하드코드된 값처럼 보이는 값을 통한 SQL Injection 공격이나 심지어 전체 코드 실행까지 발생할 수 있습니다.YAML.load()
에 대한 불안정한 호출도 있습니다.
...
class Transaction
attr_accessor :id
def initialize(num=nil)
@id = num.is_a?(Numeric) ? num : nil
end
def print_details
unless @id.nil?
print $conn.query("SELECT * FROM transactions WHERE id=#{@id}")
end
end
end
...
user = YAML.load(params[:user]);
user.print_details
...
Example 1
에서 응용 프로그램은 print_details
라는 함수가 포함되기도 하는 직렬화된 User
개체가 필요할 수 있지만 공격자는 실제로 해당 @id
속성 값이 미리 정의된 직렬화된 버전의 Transaction
개체를 제공할 수 있습니다. 따라서 다음과 같은 요청은 @id
가 숫자 값인지 여부를 확인하려는 검증 검사를 우회하도록 허용할 수 있습니다.
GET REQUEST: http://server/page?user=!ruby%2Fobject%3ATransaction%0Aid%3A4%20or%205%3D5%0A
user
매개 변수에 !ruby/object:Transaction\nid:4 or 5=5\n
가 할당되어 있음을 알 수 있습니다.@id
가 "4 or 5=5"
로 설정된 Transaction
유형의 개체가 생성됩니다. 개발자는 User#print_details()
를 호출하려고 하지만 이제 Transaction#print_details()
를 호출하게 되며, Ruby의 문자열 보간은 SELECT * FROM transactions WHERE id=4 or 5=5
쿼리를 실행하도록 SQL 쿼리가 변경된다는 것을 의미하게 됩니다. 별도의 절이 추가되었기 때문에 쿼리는 true
로 평가되며 transactions
테이블 내에서 개발자가 의도했던 단일 행 대신 모든 행을 반환하게 됩니다.YAML.load()
가 "속성 지향 프로그래밍"이라는 기술을 사용하여 호출된 경우 선언된 다른 클래스를 연결할 수 있습니다. "속성 지향 프로그래밍"은 BlackHat 2010 컨퍼런스에서 Stefan Esser가 소개한 기술입니다. 이 기술을 통해 공격자는 고유한 페이로드를 만들도록 기존의 코드를 재사용할 수 있습니다.