[ Java , Js , xml ] 웹 서비스 보안 취약점 예제 (+고치기)
1. HTTP 응답분할
응답분할이란 악의적인 사용자가 서버에 잘못된 입력을 제공하여 http 응답을 두 개로 분할하여 공격함.
헤더 조작해서 서버가 두 개 이상의 응답을 반환하도록 요구하여 정보를 탈취해갈 수 있음.
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class VulnerableServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// 사용자 입력을 URL 파라미터로 받음
String userInput = request.getParameter("user");
// 취약한 응답 헤더 설정 (사용자 입력을 검증 없이 헤더에 포함)
response.setHeader("Set-Cookie", "user=" + userInput);
// 응답 내용
response.getWriter().println("Welcome " + userInput);
}
}
해결방법은 응답에 escape 처리한다.
// 사용자 입력을 인코딩 처리하여 응답 분할을 방지
String safeInput = StringEscapeUtils.escapeHtml4(userInput);
이런식으로 escape처리하여,HTTP 헤더나 HTML 응답에 안전하게 포함시킨 후 보냄
2. 경로 조작 및 자원 삽입
취약한 코드
외부 입력(name)이 삭제할 파일의 경로설정에 사용되고 있습니다.
만일 공격자에 의해 name의 값이 ../../../rootFile.txt와 같은 값이면 의도하지 않았던 파일이 삭제되어 시스템에 악영향을 줍니다.
의도하지 않는 파일이 삭제되면 안됨!
public void bad(Properties request)
{
//...
String name = request.getProperty("filename");
if (name != null)
{
// 보안 약점: 외부 입력값이 필터링 없이 파일 생성 인자로 사용
File file = new File("/usr/local/tmp/" + name);
if (file != null) file.delete();
}
//...
}
getCanonicalPath() 메서드를 사용하여 실제 파일 경로를 확인한 후,기본 디렉토리 경로와 비교하여
사용자가 상위 디렉토리로 벗어나지 않도록 방지함.
상위 디렉토리를 벗어나지 않게하면 다른 중요한 파일들에 접근 못함!
이러한 코드를 아래와 같이 경로가 벗어나는지 확인하는 코드로 바꿔 수정
String baseDir = "/var/www/app"; // 기본 경로를 설정
String strPath = "../etc/passwd"; // 사용자가 입력한 경로 (악의적인 경우)
// 파일 경로 검증
File file = new File(baseDir, strPath); // 기본 경로와 사용자 입력 경로를 합침
String canonicalPath = file.getCanonicalPath(); // 실제 파일 경로 얻기
// 사용자가 설정한 경로가 baseDir을 벗어나는지 확인
if (!canonicalPath.startsWith(new File(baseDir).getCanonicalPath())) {
throw new SecurityException("잘못된 경로입니다!");
}
// 경로가 유효한 경우에만 디렉토리 생성
if (!file.isDirectory()) {
file.mkdirs(); // 안전하게 디렉토리 생성
}
3. 널 포인터 역참조
자바에서 null 값을 참조하는 객체에 접근하려 할 때 발생하는 오류입니다.
즉, 객체가 생성되지 않았거나 초기화되지 않은 상태에서 해당 객체의 메서드나 변수를 사용하려 할 때 NullPointerException(NPE)이 발생하게 됩니다.
public void good(ServletRequest request)
{
String myString = null;
if ((myString != null) && (myString.length() > 0)) {
IO.writeLine("The string length is greater than 0");
}
}
else로 대입안하거나 continue로 그냥 넘기고 다음 반복문으로 진행 시킴
null 할당안해도 될 것 같음.. 오류나는지 확인 필요
4. 부적절한 자원 해제 ( IO )
프로그램의 자원, 예를 들면 열린 파일디스크립터(Open File Descriptor), 힙 메모리(Heap Memory), 소켓(Socket), DB 등은 유한한 자원입니다.
이러한 자원을 할당받아 사용한 후, 더 이상 사용하지 않는 경우에는 적절히 반환하여야 하는데, 프로그램 오류 또는 에러로 사용이 끝난 자원을 반환하지 못하는 경우입니다.
예시)
ByteArrayInputStream fileOut = new ByteArrayInputStream(out.toByteArray());
여기서는 자원누수가 발생할 수 있음.
적절이 해제 안되면 프로그램이 유한한 시스템 자원을 소진할 수 있다고함.
ByteArrayInputStream 내부적으로 힙 메모리를 사용함.
파일 핸들로 운영 체제에서 한 번에 열수있는 파일의 수를 제한을 둬 Too Many Open Files와 같은 오류가 발생 가능.
해결 방법으로는
try (ByteArrayInputStream fileOut = new ByteArrayInputStream(out.toByteArray())) {
// fileOut 사용
} catch (IOException e) {
// 예외 처리
}
이런식으로 객체의 자동으로 자원을 해제할 필요는 없지만, 다른 자원과의 일관성을 위해서 try-with-resources구문으로 자동으로 닫아주고 코드에서 자원 누수를 방지할 수 있음.
5. 신뢰되지 않는 URL 주소로 자동 접속 연결
public void connectToUrl(String urlString) {
try {
// 신뢰되지 않은 외부 URL로 연결
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
// 연결 후 처리
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
System.out.println("Response Content: " + content.toString());
} catch (Exception e) {
System.out.println("Error occurred: " + e.getMessage());
}
}
보안 개선 방법은 화이트리스트로 특정 도메인 , ip를 미리 작성한 후 그것과 비교
public void connectToTrustedUrl(String urlString) {
try {
// 신뢰할 수 있는 도메인 검증
if (!urlString.startsWith("https://trusted.com")) {
throw new IllegalArgumentException("Untrusted URL");
}
URL url = new URL(urlString);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
// 연결 후 처리
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
System.out.println("Response Content: " + content.toString());
} catch (Exception e) {
System.out.println("Error occurred: " + e.getMessage());
}
}
6. 크로스사이트 스크립트 (XSS Error Message)
{
response.sendError(404, "param " + data);
}
data값에 악의적인 스크립트 포함시켜 사용자에게 실행을 유도 시킬 수 있으므로 replace를 작성하자
data.replaceAll("<","")
7.오류 상황 대응 부재
try catch나 메소드 반환값에 대한 적절한 예외처리 필요합니다,
catch 블록이 비어있는 문제 해결 catch블록에 들어가는 내용은
- 오류로깅 : 발생한 예외의 정보를 로그로 남기는 것이 좋음
- 사용자에게 알리기
- 복구로직 : 기본값으로 설정하는 로직을 넣거나 다시 시도하는 로직을 넣어줌
8. 오류 상황 미수신
예외를 발생시키면 반드시 처리하게 throw new ~~~Exception시에 해당 Exception처리하는 문 필요합니다.
9.주석문 안에 포함된 시스템 주요정보
주석안에 원래 로직 또는 변수명 id,pass 같은 값 있으면 제거해야 됨.
10.중요한 자원에 대한 잘못된 권한 설정 (File)
File file = new File(sPath);
if (file.exists() == false) {
file.mkdirs();
}
해당 코드를
File dir = new File(sPath);
// 1. 디렉토리 존재 여부 체크
if (!dir.exists()) {
// 2. 먼저 권한 검증 (필요시, 부모 디렉토리나 경로에 대한 접근 권한도 검증)
if (!dir.getParentFile().canWrite()) {
throw new SecurityException("Cannot write to the parent directory.");
}
// 3. 디렉토리 생성
boolean success = dir.mkdirs();
if (success) {
// 4. 생성 후 권한 설정
boolean isExecutableSet = dir.setExecutable(false, true); // 모든 사용자에게 실행 권한 없음
boolean isReadableSet = dir.setReadable(true); // 모든 사용자에게 읽기 권한 부여
boolean isWritableSet = dir.setWritable(false, true); // 모든 사용자에게 쓰기 권한 없음
if (isExecutableSet && isReadableSet && isWritableSet) {
System.out.println("Directory created and permissions set successfully.");
} else {
throw new SecurityException("Failed to set the directory permissions.");
}
} else {
throw new IOException("Failed to create the directory.");
}
} else {
System.out.println("Directory already exists.");
}
디렉토리 생성 전에 권한 검증을 수행하면 보안적인 위험을 줄일 수 있음.
권한 검증 후 디렉토리를 생성하므로 , 권한이 제대로 설정되지 않은 디렉토리가 생성되거나 사용하는 시스템에 위험을 초래할 가능성 있음.
근데 보통은 생성한 후에 한다고함 code-ray에서는 후에 사용하면 계속 잡혀서 해당 변수들을 위로 넣었는데 그건 올바르지 않는 방법
생성한 후에 하는 이유는
- 권한 설정은 이미 존재하는 디렉토리나 파일에 적용되는 것이 일반적 디렉토리가 없으면 검증할 이유가 없기 때문..
11. 적절하지 않은 난수값 사용
Math.random()은 예측이 가능한다고해서 사용하면 안된다고 한다.
보안 대책으로 난수값 예측이 어려운 함수 사용해야함.
// 랜덤 인덱스 생성 함수
function getRandomIndex(length) {
const array = new Uint32Array(1); // 32비트 정수 배열 생성
window.crypto.getRandomValues(array); // 보안적인 난수 생성
return array[0] % length; // 배열 길이에 맞춰 인덱스 반환
}