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

구현단계 보안약점 기준 - 경로 조작 및 자원 삽입

브루노W 2024. 9. 12. 14:50
유형 입력데이터 검증 및 표현
보안약점 경로 조작 및 자원 삽입
개요 검증되지 않은 외부입력값으로 파일 및 서버 등 시스템 자원에 대한 접근 혹은 식별을 허용할 경우, 입력값 조작으로 시스템이 보호하는 자원에 임의로 접근할 수 있는 보안약점이다.

경로 조작 및 자원 삽입 약점을 이용하여 공격자는 자원의 수정, 삭제, 시스템 정보누출, 시스템 자원 간 충돌로 인한 서비스 장애 등을 유발시킬 수 있다.
보안대책 외부의 입력을 자원(파일, 소켓의 포트 등)의 식별자로 사용하는 경우, 적절한 검증을 거치도록 하거나 사전에 정의된 적합한 리스트에서 선택되도록 한다.

특히, 외부의 입력이 파일명인 경우에는 경로 순회(directory traversal) 공격의 위험이 있는 문자(" / \ .. 등)를 제거할 수 있는 필터를 이용한다.
진단방법
경로 생성과 자원제어를 위한 코드에 외부 입력값을 사용하는지 여부를 확인한 후, 외부 입력값을 사용하는 경우 적절한 필터링이 이루어지거나 리스트의 값이 선택되는지 확인한다.
 
또한, 시스템 경로를 얻기 위해 경로를 직접 입력하는 대신 시스템 함수를 호출하여 경로를 생성하는 것이 바람직하므로, 이러한 함수가 사용되는지 확인한다.
연관된 설계단계 기준 시스템 자원 접근 및 명령어 수행 입력값 검증

 

코드예제

 

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

// 외부로부터 입력받은 값을 검증 없이 사용할 경우 안전하지 않다.
String fileName = request.getParameter("P");
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
FileInputStream fis = null;
try {
	response.setHeader("Content-Disposition", "attachment;filename="+fileName+";");

	// 외부로부터 입력받은 값이 검증 또는 처리 없이 파일처리에 수행되었다.
	fis = new FileInputStream("C:/datas/" + fileName);
	bis = new BufferedInputStream(fis);
	bos = new BufferedOutputStream(response.getOutputStream());

외부 입력값(P)이 버퍼로 내용을 옮길 파일의 경로설정에 사용되고 있다. ../../../rootFile.txt 와 같은 값이 전달되면 의도하지 않았던 파일의 내용이 버퍼에 쓰일 수 있다.

 

● 안전한 코드 예 (Java)

String fileName = request.getParameter("P");
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
FileInputStream fis = null;
try {
	response.setHeader("Content-Disposition", "attachment;filename="+fileName+";");

	// 외부 입력받은 값을 경로순회 문자열(./\)을 제거하고 사용해야한다.
	filename = filename.replaceAll("\\.", "").replaceAll("/", "").replaceAll("\\\\", "");
	fis = new FileInputStream("C:/datas/" + fileName);
	bis = new BufferedInputStream(fis);
	bos = new BufferedOutputStream(response.getOutputStream());
	int read;

	while((read = bis.read(buffer, 0, 1024)) != -1) {
		bos.write(buffer,0,read);
	}
}

외부 입력값에 대하여 상대경로를 설정할 수 없도록 경로순회 문자열( / \ & .. 등)을 제거하고 파일을 경로설정에 사용한다.

 

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

public class ShowHelp {
	private final static String safeDir = "c:\\help_files\\";
	public static void main(String[] args) throws IOException {
		String helpFile = args[0];
		try (BufferedReader br = new BufferedReader(new FileReader(safeDir + helpFile))) {
			String line;
			while ((line = br.readLine()) != null) {
				System.out.println(line);
			}

		}

args[0] 값이 "..\..\..\windows\system32\drivers\etc\hosts" 와 같이 경로조작 문자열을 포함한 입력이 들어오는 경우 접근이 제한된 경로의 파일을 열람할 수 있다.

 

● 안전한 코드 예 (Java)

public class ShowHelpSolution {
	private final static String safeDir = "c:\\help_files\\";
	// 경로조작 문자열 포함 여부를 확인하고 조치 후 사용하도록 한다.
    
	public static void main(String[] args) throws IOException {
		String helpFile = args[0];
		if (helpFile != null) {
			helpFile = helpFile.replaceAll("\\.{2,}[/\\\\]", "");
		}
        
		try (BufferedReader br = new BufferedReader(new FileReader(safeDir + helpFile))) {

외부 입력값에 대하여 null 여부를 체크하고, 외부에서 입력되는 파일 이름에서 경로조작 문자열 제거 조치 후 사용한다.

 

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

// 외부 입력 값이 검증 없이 파일처리에 사용 되었다.
string file = Request.QueryString["path"];
if (file != null)
{
	File.Delete(file);
}

외부 입력값을 파일명에 바로 사용하고 있다.

 

● 안전한 코드 예 (C#)

string file = Request.QueryString["path"];
if (file != null)
{
	// 경로조작 문자열이 있는지 확인하고 파일 처리를 하도록 한다.
	if (file.IndexOf('\\') > -1 || file.IndexOf('/') > -1)
	{
		Response.Write("Path Traversal Attack");
	}
	else
	{
		File.Delete(file);
	}
}

외부 입력값에 경로 조작 문자열을 제거하여 조작 위험을 없앨 수 있다.

 

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

char* filename = getenv("reportfile");
FILE *fin = NULL;
// 외부 설정 값에서 받은 파일 이름을 그대로 사용한다.
fin = fopen(filename, "r");
while (fgets(buf, BUF_LEN, fin)) {
	// 파일 내용 출력
}

외부 입력값을 파일 경로로 바로 사용하고 있다. 환경 변수 reportfile을 조작하여 디렉토리 경로를 조작할 수 있다.

 

● 안전한 코드 예 (C)

FILE *fin = NULL;
regex_t regex;
Int ret;
char* filename = getenv("reportfile");
ret = regcomp(&regex, ".*\\.\\..*", 0);
// 경로 조작 가능성 있는 문자열 탐지
ret = regexec(&regex, filename, 0, NULL, 0);
If (!ret) {
	// 경로 조작 문자열 발견, 오류 처리
}
// 필터링된 파일 이름으로 사용
fin = fopen(filename, "r");
while (fgets(buf, BUF_LEN, fin)) {
	// 파일 내용 출력
}

외부에서 불러온 파일 이름을 그대로 사용하지 않고 경로 조작 가능성이 있는 문자열을 검증하고 사용한다.

 

 

진단방법

 

 

디렉토리 경로나 자원에 대한 접근 경로 생성, 자원을 제어하기 위한 코드는 외부의 입력값이 경로 생성과 자원 제어에 직접적으로 영향을 주어서는 안 된다. 따라서, 외부 입력값이 해당 작업에 직접적인 영향을 미치는지 확인한다.

 

먼저 경로 생성과 자원 제어를 위한 코드에 외부 영향값을 사용하는지 여부를 확인한 후, 외부 입력값을 사용하는 경우 적절한 필터링이 이루어지거나 리스트의 값이 선택되는지 확인한다.

 

또한, 시스템 경로를 얻기 위해 경로를 직접 입력하는 대신 시스템 함수를 호출하여 경로를 생성하는 것이 바람직하므로, 이러한 함수가 사용되는지 확인한다.

 

[경로 조작]
파일객체를 사용하는지 확인하고(①), 해당 파일에 대한 접근이 외부에서 직접 접근하는지 확인한다(②).
파일객체가 가리키는 경로에 외부 입력 값이 경로 순회 문자열에 대한 제거없이 사용되면 경로의 변경이 가능하게 되어 취약하다고 판정한다.

 

● 일반적인 진단의 예

…
String basePath = "/web/data/";
String filename = request.getParameter("filename"); ······························②
String fullPath = basePath + filename;
…
File f = new File(fullPath); ·····················································①
if (f.isFile()) {
    FileInputStream in = new FileInputStream(fullPath);
    ServletOutputStream os = response.getOutputStream();
    byte[] buf = new byte[1024];
    int len = 0;
    while ((len = in.read(buf)) > 0) {
    	os.write(buf, 0, len);
	}
}
…

 

[자원 삽입]
파일명, 소켓의 포트 등과 같은 자원을 사용하는지 확인하고(①), 해당 자원에 대한 접근이 외부에서 직접 접근하는지 확인한다(②). 

직접 접근하지 않고 매핑표나 리스트를 가질 경우엔 기본적으로 안전하다고 판단하고, 그 외의 경우엔 취약하다고 판정한다.

 

● 일반적인 진단의 예

public class U99 {
    public void f() throws IOException {
        int def = 1000;
        ServerSocket serverSocket;
        Properties props = new Properties();
        String fileName = "file_list";
        FileInputStream in = new FileInputStream(filename);
        props.load(in);

        String service = props.getProperty("Service No");
        int port = Integer.parseInt(service); ··································②

        if (port != 0)
        	serverSocket = new ServerSocket(port + 3000); ······················①
        else
        	serverSocket = new ServerSocket(def + 3000); ·······················①
        ServerSocket.close();
    }
}

 

 

● 정탐코드

// < EgovFileCmprs.jsp >
<%
    boolean isCompressed = false;
    String source = request.getParameter("source");
    String target = request.getParameter("target");
    if (source != null && source.length() > 0 && target != null && target.length() > 0) {
        isCompressed = EgovFileCmprs.cmprsFile(source, target);
    …
%>

// < EgovFileCmprs.java >
public static boolean cmprsFile(String source, String target) throwsException {
    boolean result = false;
    int cnt = 0;
    FileInputStream finput = null;
    FileOutputStream foutput = null;
    ZipOutputStream zoutput = null;
    String source1 = source.replace('\\', FILE_SEPARATOR).replace('/', FILE_SEPARATOR);
    String target1 = target.replace('\\', FILE_SEPARATOR).replace('/', FILE_SEPARATOR);
    File srcFile = new File(source1);​

EgovFileCmprs.jsp에서 외부 입력값인 source를 받아서  static 함수인 EgovFileCmprs.cmprsFile를 호출하고 있다. 이 함수 내에서 source 파라미터를 source1 변수에 저장하고,  그 변수를 사용하여 파일을 생성하고 있어 취약하다고 판정한다.

 

 

● 오탐코드

File file = new File(getServletConfig().getServletContext().getRealPath("/") + "/jsp/gis/gcc/cmm/xml/" + (String) tempChartImgList.get(k));

사용되는 getRealPath(), getServletContext() 등 내부 함수는 소스가 안전하므로 취약하지 않다고 판정한다.

 

 

● 오탐코드

File[] f_list = f.listFiles( new FilenameFilter() {
	public boolean accept(File dir, String name) {
		String org_code = SystemierConfig.getPropertiesBean().getProperty("is.OrgCode");

		if(name.matches(org_code + ".*CVDTST.*" + ".txt")) {
			return true;
		}
		return false;
	}
});

BufferedReader in = new BufferedReader( new FileReader(f_list[0].getPath()) );

일반적으로 프레임워크를 사용하면서 시스템 프로퍼티에서 가져오는 명령어는 취약하지 않다고 판정한다.