본문 바로가기
소프트웨어(SW) 보안약점 진단원/구현단계 보안약점 제거 기준

구현단계 보안약점 기준 - 서버사이드 요청 위조

by 브루노W 2025. 5. 26.
유형 입력데이터 검증 및 표현
보안약점 서버사이드 요청 위조
개요
적절한 검증 절차를 거치지 않은 사용자 입력값을 서버 간의 요청에 사용하여 악의적인 행위가 발생할 수 있는 보안약점이다.
 
외부에 노출된 웹 서버에 취약한 애플리케이션이 존재하는 경우 공격자는 URL 또는 요청문을 위조하여 접근통제를 우회하는 방식으로 비정상적인 동작을 유도하거나 신뢰된 네트워크에 있는 데이터를 획득할 수 있다.
보안대책
식별할 수 있는 범위 내에서 사용자의 입력 값을 다른 시스템의 서비스 호출에 사용하는 경우, 사용자의 입력 값을 화이트 리스트 방식으로 필터링한다.
 
사용자가 지정하는 무작위의 URL을 받아들여야 한다면 내부의 URL을 블랙 리스트로 지정하여 필터링한다.
또한 동일한 내부 네트워크에 있더라도 기기 인증, 접근 권한을 확인하여 요청이 이루어질 수 있도록 한다.
진단방법
다른 시스템의 서비스를 호출하는 함수가 존재하는지 확인하고 다른 시스템을 호출할 때 사용되는 입력값이 신뢰할 수 있는 값인지 확인한다. 만약 입력값이 신뢰할 수 없고 별도의 검증 절차가 없으면 안전하지 않다고 판정한다.
연관된 설계단계 기준

 

코드예제

 

※ 참고 : 삽입 코드의 예

설명 삽입 코드의 예
내부망 중요 정보 획득
 • http://site_example.com/connect?url=http://192.168.0.45/member/list.json
외부 접근 차단된 admin 페이지 접근  • http://site_example.com/connect?url=http://192.168.0.45/admin
도메인 체크를 우회하여 중요 정보 획득 • http://site_example.com/connect?url=http://site_example.com:x@192.168.0.45/member/list.json
단축 URL을 이용한 Filter 우회  • http://site_example.com/connect?url=http://bit.ly/sdjk3kjhkl3
도메인을 사설IP로 설정해 중요정보 획득 • http://site_example.com/connect?url=http://internal.site.com/member/list.json
서버내 파일 열람  • http://site_example.com/connect?url=http://attack/fileview.html
 

● 안전하지 않은 코드 예 (Java)

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // 사용자 입력값(url)을 검증 없이 사용하여 안전하지 않다.
    URL url = new URL(req.getParameter("url"));
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
}

 

 

● 안전한 코드 예 (Java)

public class Connect {
    // key, value 형식으로 URL의 리스트를 작성한다.
    private Map<String, URL> urlMap;

    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        // 사용자에게 urlMap의 key를 입력받아 urlMap에서 URL값을 참조한다.
        URL url = urlMap.get(req.getParameter("url"));
        // urlMap에서 참조한 값으로 Connection을 만들어 접속한다.
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    }
}

 

 

진단방법
 
다른 시스템의 서비스를 호출하는 함수가 존재하는지 확인하고(①) 다른 시스템을 호출할 때 사용되는 입력값이(②) 신뢰할 수 있는 값인지 확인한다.
만약 입력값이 신뢰할 수 없고 별도의 검증 절차가 없으면 안전하지 않다고 판정한다.

 

 

● 일반적인 진단의 예

public class G03 extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String url = req.getParameter("url"); ·················································②

        InputStream inputStream = null;
        OutputStream outputStream = null;
        //사용자에게 url을 입력 받는다.
        URL u = new URL(url);
        res.setHeader("content-disposition","attachment;fileName=''");

        int length;
        byte[] bytes = new byte[1024];
        // 입력받은 URL을 stream으로 생성한다.
        inputStream = u.openStream(); ························································①
        outputStream = res.getOutputStream();
        while ((length = inputStream.read(bytes)) > 0) {
        	outputStream.write(bytes, 0, length);
        }
    }
}

 

 

● 정탐코드

public class ConnectProperties {
    FileReader newFile = new FileReader("File.properties");
    Properties properties = new Properties();
    properties.load(newFile);

    protected void doGet(HttpServletRequestreq, HttpServletResponseresp) throws IOException{
        URL url = new URL(properties.getProperty("connectUrl"));
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    }
}

openConnection에 사용하는 URL을 properties에서 참조하고 있다. properties는 다른 공격 등으로 위변조 될 수 있으므로 취약하다고 판정한다.

 

● 정탐코드

private String getRemoteContent(String url) throws IOException {
    BufferedReader in = new BufferedReader(new InputStreamReader(
        new URL(url).openStream()));
        return FileCopyUtils.copyToString(in);
}

public String getContent(String inUrl) throws IOException {
    try {
        String str = getRemoteContent(inUrl);
        str = str.replace("<head>", "<head><base href='" + inUrl
        + "' /><base target='_blank' /><script>top.studio.startPageIFrameLoaded();"
        + "</script>");
        return str;
    } catch (Exception e) {
        return "";
    }
}

inURL 값이 추가적인 검증 없이 URL(url).openStream()에 전달된다. 이때, 공격자가 inURL 입력값을 악의적으로 조작하여 피해를 끼칠 수 있으므로 취약하다고 판정한다.

 

 

● 정탐코드

public void doGet(HttpServletRequest request, HttpServletResponse response) {
    String host = request.getParameter("host");

    byte[] bytes = getImage(host, defaultBytes);
    if (bytes != null) {
    	writeBytesToStream(bytes, response);
    }
}
 
private byte[] getImage(String host, byte[] defaultImage) {
    byte[] bytes = getImage("http://" + host + "/favicon.ico");
    ...
}

공격자가 host 파라미터를 조작하여 내부 서버의 정보를 조회할 수 있음을 보여준다. 이때, host 값을 별도의 검증 없이 전달하여 내부 서버의 데이터(secrets.txt) 파일이 외부로 유출될 수 있으므로 취약하다고 판정한다.

 

삽입 코드의 예 : /getFavicon?host=192.168.176.1:8080/secrets.txt?

 

 

● 오탐코드

<?php
    require_once('./htmlpurifier/library/HTMLPurifier.includes.php');
    $purifier = new HTMLPurifier();

    $ch = curl_init();
    $url = $_GET['url'];
    $urlinfo = parse_url($url); // URL 파싱
    $scheme = $urlinfo['scheme'];
    $host = $urlinfo['host'];
    $ip = gethostbyname($host);
    if ($ip === "169.254.169.254") { // 퍼블릭클라우드 메타데이터 서비스 IP를 검증
    	die("Invalid host");
    } else if ($scheme !== 'http' && $scheme !== 'https') { // HTTP(S) 스킴 체크
    	die("Invalid scheme");
    }
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $html = curl_exec($ch);
    echo $purifier->purify($html);

Public Cloud의 메타데이터 서비스 주소인 169.254.169.254를 블랙 리스트 방식으로 필터링하여 에러를 리턴하여 메타서버의 크리덴셜 조회를 차단할 수 있으므로 취약하지 않다고 판정한다.

 

 

● 오탐코드

private static String getRemoteContent(String url) throws IOException {
    if ( isPrivateIP(url) == true ) {
    	return "invalid url";
    }
    BufferedReader in = new BufferedReader(new InputStreamReader(
    new URL(url).openStream()));
    return FileCopyUtils.copyToString(in);
}

public static boolean isPrivateIP(String r){
    // 블랙 리스트 목록 (lookback 주소, IPv4 및 IPv6 주소)
    String private_ip[] = {
        // loopback addresses
        "127.", "0.",
        // IP V4 prefix for private addresses
        "10.","172.16.", "172.17.", "172.18.", "172.19.", "172.20.", "172.21.","172.22.", "172.23.", "172.24.", "172.25.", "172.26.", "172.27.","172.28.", "172.29.", "172.30.", "172.31.", "192.168.", "169.254.",
        // IP V6 prefix for private addresses
        "fc", "fd", "fe", "ff", "::1"
	};

    for(int i = 0; i<private_ip.length; i++){
    	if(r.toLowerCase().trim().startsWith(private_ip[i])){
    		return true;
        }
    }
    return false;
}

내부 대역의 IP들에 대한 접근 시도를 블랙 리스트 방식으로 필터링한다. isPrivateIP() 메소드를 호출하여 입력 값을 블랙 리스트 목록과 비교하여 허용되지 않은 접근에 대하여 오류를 리턴한다. 이 경우, 블랙 리스트를 우회할 수 있는 다양한 공격을 감안해서 필터링하고 있는지 검토 후 취약 여부를 판단할 필요가 있다.

 

 

※ 문자열 인코딩 후 SSRF 예시

SSRF 예시 URL 설명
/ssrf.php?url=http://425.510.425.510/ 
오버플로우 된 형식의 점이 포함된 IP
/ssrf.php?url=http://2852039166/ 
점이 포함되지 않은 10진수 형식
/ssrf.php?url=http://7147006462/ 
점이 포함되지 않은 오버플로된 형식
/ssrf.php?url=http://0xA9.0xFE.0xA9.0xFE/ 
점이 포함된 16진수 형식
/ssrf.php?url=http://0xA9FEA9FE/ 
점이 포함되지 않은 16진수 형식
/ssrf.php?url=http://0x41414141A9FEA9FE/ 
점이 포함되지 않은 16진수의 오버플로된 형식
/ssrf.php?url=http://0251.0376.0251.0376/ 
점이 포함된 8진수 형식
/ssrf.php?url=http://0251.00376.000251.000037/  패딩이 포함된 점 포함 8진수 형식

 

※ IDNA2003 변환을 이용한 SSRF 예시

URL
설명
/ssrf.php?url=http://ⓖⓞⓞⓖⓛⓔ.com
http://google.com (원문자는 영문으로 인식됨)
/ssrf.php?url=http://wordpreß.com  http://wordpress.com (독일어로 ß는 ‘ss’가 됨)

 

※ Broken Parser를 이용한 SSRF 예시

URL
설명
/ssrf.php?url=https://evil-host#expected-host 
#을 추가하여 탐지룰 회피 시도
/ssrf.php?url=https://expected-host@evil-host  @을 추가하여 탐지룰 회피 시도

 

※ SSRF를 이용한 LFI 예시

URL
설명
/ssrf.php?url=file:///etc/passwd 
로컬 파일을 읽음
/ssrf.php?url=ldap://localhost:1337/%0astats%0aquit  로컬 LDAP을 읽음