SKS 공부 - 2025-12-22

SKS 홈으로
39회차 공부

한줄요약:
    애플리케이션 보안 - XSS와 CSRF는 사용자의 브라우저 권한을 악용하는 대표적인 웹 공격이며, 이를 방지하려면 개발 단계에서 White Box Testing과 같은 소스코드 진단을 통해 입력값 검증, 안전한 세션 관리, 중요 정보 암호화 등의 보안 코딩을 적용해야 합니다.

오늘 공부한 내용:
  • 
    
    
    ## 🛡️ XSS 공격 및 소스코드 취약점 분석 요약
    
    
    ---
    
    ## 1. Chapter 05: XSS 및 CSRF 공격
    
    ### A. 쿠키(Cookie)와 사용자 인증
    
    * **개념**: 웹 서버가 사용자의 브라우저에 저장하는 4KB 이하의 텍스트 파일입니다.
    * **용도**: 세션 유지(로그인 상태), 개인화 설정, 장바구니, 이용 행태 추적 등.
    * **보안 중요성**: 쿠키에 세션 ID나 민감 정보가 담기므로, 공격자가 이를 탈취하면 사용자 권한을 도용할 수 있습니다.
    
    ### B. XSS (Cross-Site Scripting)
    
    공격자가 웹 페이지에 **악성 스크립트를 삽입**하여, 해당 페이지를 열람하는 사용자의 브라우저에서 스크립트가 실행되게 하는 공격입니다.
    
    | 유형 | 설명 | 주요 특징 |
    | --- | --- | --- |
    | **Stored XSS** | 게시판, 프로필 등 **DB에 스크립트가 저장**됨 | 게시물 열람 시 모든 사용자에게 영향 (파괴력 큼) |
    | **Reflected XSS** | **URL 파라미터** 등에 포함된 스크립트가 즉시 반사됨 | 악성 링크 클릭을 유도해야 함 (1회성) |
    
    ### C. CSRF (Cross-Site Request Forgery)
    
    사용자가 자신의 의지와 무관하게 **공격자가 의도한 행위(비밀번호 변경, 송금 등)**를 특정 웹사이트에 요청하게 만드는 공격입니다.
    
    * **차이점**: XSS는 정보를 '탈취'하는 데 목적이 있다면, CSRF는 특정 '동작'을 수행하게 하는 데 목적이 있습니다.
    
    ---
    
    ## 2. Chapter 06: 소스코드 취약점 분석
    
    애플리케이션의 보안성을 확보하기 위해 코드를 직접 분석하여 잠재적 취약점을 찾아내는 과정입니다.
    
    ### A. 분석 방법론
    
    1. **Black Box Testing**: 소스코드 없이 외부 인터페이스를 통해 공격 시도 (해커의 관점).
    2. **White Box Testing**: 소스코드 내부 로직을 직접 분석 (개발자/보안 진단원 관점).
    3. **Gray Box Testing**: 외부 취약점 점검과 코드 분석을 혼합하여 효율성 극대화.
    
    ### B. 주요 취약점 분석 사례
    
    * **입력값 검증 미비**: SQL 인젝션, XSS, 파일 업로드 취약점 등 대부분의 보안 사고 원인입니다. `getRawParameter`와 같이 필터링 없이 값을 받는 함수 사용을 주의해야 합니다.
    * **세션 처리 및 접근 통제**: 쿠키 값(`getCookies`)만으로 권한을 판단하거나, 관리자 페이지에 세션 체크 코드가 누락된 경우(**강제 브라우징**) 발생합니다.
    * **중요 정보 노출**:
    * **하드 코딩**: 패스워드나 DB 접속 정보를 소스코드에 직접 기입하는 행위.
    * **평문 전송**: HTTPS(SSL) 없이 데이터를 전송하여 스니핑에 노출됨.
    * **주석 처리**: 개발 시 남겨둔 아이디/패스워드가 주석(`//`, `/* */`)에 남아있는 경우.
    
    
    
    ### C. 언어별 주요 보안 함수 (Java 기준)
    
    * **입력값 처리**: `getParameter()`, `getQueryString()` 등.
    * **세션/쿠키**: `getCookies()`, `getSession()` 등.
    * **DB 연결**: `DriverManager.getConnection()` (하드코딩 주의).
    
    ---
    
    
    <12. 22일>
    
    (교제 외) command injection을 해보자.
    deepseek.com 을 이용 : chatgpt 등은 보안 취약성 등에 대한 답을 안전상 이유로 잘 해주지 않음
    질문 1 : 우분투 jsp 서버에서 ip를 입력받아, 목적지까지 ping 시스템콜을 호출하고, 그 결과를 웹페이지로 실시간 출력하는 jsp를 하나의 파일로 간단하게 작성하시오. 단, 보안에 취약하여 command injection이 발생 가능하도록 작성하시오.
    질문 2: 위의 코드에서 스타일등 필수기능이 아닌 부분은 제외하고, 최대한 간단하게 작성하라. count도 기본으로 5번 수행하도록 하고, 입력은 IP만 받도록 하자.
    
    보안상 이유로 톰캣9 까지만 아래 코드로 동작
    <%@ page import="java.io.*" %>
    <%
    String ip = request.getParameter("ip");
    if (ip != null && !ip.trim().isEmpty()) {
        String[] command = {"/bin/bash", "-c", "ping -c 5 " + ip};
        Process p = Runtime.getRuntime().exec(command);
        BufferedReader stdInput = new BufferedReader(new InputStreamReader(p.getInputStream()));
        String s;
        while ((s = stdInput.readLine()) != null) {
            out.println(s + "<br>");
        }
        return;
    }
    %>
    <html>
    <body>
    <form method="post">
    IP: <input type="text" name="ip">
    <input type="submit" value="Ping">
    </form>
    </body>
    </html>
    
    <경로 : /var/lib/tomcat10/webapps/ROOT/ping.jsp>
    
    Tomcat10 보안 설정 관련
    sudo setcap cap_net_raw+ep /usr/bin/ping
    vi /usr/lib/systemd/system/tomcat10.service
    [Service]
    AmbientCapabilities=CAP_NET_RAW
    NoNewPrivileges=false
    
    
    sudo systemctl daemon-reexec
    sudo systemctl restart tomcat10
    
    
    ** 소스코드 보안 관리 (jsp 기준)
       : 소스코드의 제일 상위 디렉토리에서 시스템콜을 바로 호출하는 함수를 찾아서, 보안상 취약점을 검사 한다. 
    grep "getRuntime().exec" -r /var/lib/tomcat10/webapps/ROOT
    
    
    
    
    XSS 취약성이 존재하는 게시판 만들기
    deepseek.com 
    질문1 : jsp코드를 이용하여 xss공격이 가능한 익명게시판을 만들어라. 게시판은 글쓰기(이름, 제목, 내용), 목록(제목), 읽기 기능을 제공한다.
    질문 2: java코드를 사용하지 않고, jsp만으로 코드를 최대한 간단하게 작성하시오. 스타일등은 생략하시오. 데이터베이스 생성부터 만드시오. 데이터베이스는 1.1.1.1의 mydb 계정을 사용하는 mariadb 입니다.
    
    -- 데이터베이스 생성
    CREATE DATABASE IF NOT EXISTS C;
    USE cloud_db;
    
    -- 게시판 테이블 생성
    CREATE TABLE IF NOT EXISTS board (
        id INT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(50) NOT NULL,
        title VARCHAR(200) NOT NULL,
        content TEXT NOT NULL,
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    );
    
    
    
    <mysql -u mydb -p -h 192.xxx.xxx.xxx>
    
    초기 설정 파일 (config.jsp)
    
    <%-- config.jsp --%>
    <%@ page import="java.sql.*" %>
    <%
        // 데이터베이스 연결 정보
        String dbUrl = "jdbc:mariadb://192.168.186.130:3306/cloud_db";
        String dbUser = "mydb";
        String dbPass = "abcd1234";
        
        // 데이터베이스 드라이버 로드
        Class.forName("org.mariadb.jdbc.Driver");
        
        // 데이터베이스 연결 함수
        public Connection getConnection() throws SQLException {
            return DriverManager.getConnection(dbUrl, dbUser, dbPass);
        }
    %>
    
    
    메인 페이지 (index.jsp)
    <%@ page contentType="text/html;charset=UTF-8" %>
    <%@ page import="java.sql.*" %>
    <!DOCTYPE html>
    <html>
    <head>
        <title>XSS 게시판</title>
    </head>
    <body>
        <h1>익명 게시판</h1>
        <a href="write.jsp">글쓰기</a>
        <hr>
        
        <table border="1" width="100%">
            <tr>
                <th>번호</th>
                <th>제목</th>
                <th>작성자</th>
                <th>날짜</th>
            </tr>
            
            <%
                Connection conn = null;
                Statement stmt = null;
                ResultSet rs = null;
                
                try {
                    Class.forName("org.mariadb.jdbc.Driver");
                    conn = DriverManager.getConnection("jdbc:mariadb://192.168.186.130:3306/cloud_db", "mydb", "abcd1234");
                    stmt = conn.createStatement();
                    rs = stmt.executeQuery("SELECT * FROM board ORDER BY id DESC");
                    
                    while(rs.next()) {
            %>
            <tr>
                <td><%= rs.getInt("id") %></td>
                <td><a href="read.jsp?id=<%= rs.getInt("id") %>">
                    <%= rs.getString("title") %></a></td>
                <td><%= rs.getString("name") %></td>
                <td><%= rs.getString("created_at") %></td>
            </tr>
            <%
                    }
                } catch(Exception e) {
                    out.println("에러: " + e.getMessage());
                } finally {
                    if(rs != null) try { rs.close(); } catch(Exception e) {}
                    if(stmt != null) try { stmt.close(); } catch(Exception e) {}
                    if(conn != null) try { conn.close(); } catch(Exception e) {}
                }
            %>
        </table>
    </body>
    </html>
    
    
    글쓰기 페이지 (write.jsp)
    <%@ page contentType="text/html;charset=UTF-8" %>
    <!DOCTYPE html>
    <html>
    <head>
        <title>글쓰기</title>
    </head>
    <body>
        <h1>글쓰기</h1>
        <form action="save.jsp" method="post">
            이름: <input type="text" name="name" value="익명"><br><br>
            제목: <input type="text" name="title" size="50"><br><br>
            내용: <br>
            <textarea name="content" rows="10" cols="50"></textarea><br><br>
            <input type="submit" value="저장">
            <a href="index.jsp">취소</a>
        </form>
    </body>
    </html>
    
    
    저장 처리 (save.jsp)
    <%@ page contentType="text/html;charset=UTF-8" %>
    <%@ page import="java.sql.*" %>
    <%
        request.setCharacterEncoding("UTF-8");
        
        String name = request.getParameter("name");
        String title = request.getParameter("title");
        String content = request.getParameter("content");
        
        if(name == null || name.trim().equals("")) {
            name = "익명";
        }
        
        Connection conn = null;
        PreparedStatement pstmt = null;
        
        try {
            Class.forName("org.mariadb.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mariadb://192.168.186.130:3306/cloud_db", "mydb", "abcd1234");
            
            String sql = "INSERT INTO board (name, title, content) VALUES (?, ?, ?)";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, name);
            pstmt.setString(2, title);
            pstmt.setString(3, content);
            
            pstmt.executeUpdate();
            response.sendRedirect("index.jsp");
            
        } catch(Exception e) {
            out.println("저장 실패: " + e.getMessage());
        } finally {
            if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
            if(conn != null) try { conn.close(); } catch(Exception e) {}
        }
    %>
    
    
    
    
    글 읽기 페이지 (read.jsp)
    <%@ page contentType="text/html;charset=UTF-8" %>
    <%@ page import="java.sql.*" %>
    <%
        String id = request.getParameter("id");
        if(id == null) {
            response.sendRedirect("index.jsp");
            return;
        }
        
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        String name = "";
        String title = "";
        String content = "";
        String date = "";
        
        try {
            Class.forName("org.mariadb.jdbc.Driver");
            conn = DriverManager.getConnection("jdbc:mariadb://192.168.186.130:3306/cloud_db", "mydb", "abcd1234");
            
            String sql = "SELECT * FROM board WHERE id = ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, Integer.parseInt(id));
            rs = pstmt.executeQuery();
            
            if(rs.next()) {
                name = rs.getString("name");
                title = rs.getString("title");
                content = rs.getString("content");
                date = rs.getString("created_at");
            }
        } catch(Exception e) {
            out.println("에러: " + e.getMessage());
        }
    %>
    <!DOCTYPE html>
    <html>
    <head>
        <title><%= title %></title>
    </head>
    <body>
        <h1><%= title %></h1>
        <div>작성자: <%= name %></div>
        <div>작성일: <%= date %></div>
        <hr>
        <!-- XSS 취약점: content를 필터링 없이 출력 -->
        <div><%= content %></div>
        <hr>
        <a href="index.jsp">목록</a>
        <a href="write.jsp">글쓰기</a>
        
        <%
            if(rs != null) try { rs.close(); } catch(Exception e) {}
            if(pstmt != null) try { pstmt.close(); } catch(Exception e) {}
            if(conn != null) try { conn.close(); } catch(Exception e) {}
        %>
    </body>
    
    
    
    
    
    (미션) 글을 저장하는 save.jsp에서 악성 script 삽입 공격(XSS)를 차단해 보자.
    
        if(name == null || name.trim().equals("")) {
            name = "익명";
        }
        if(content!= null) 
             content = content.replace("<script>", "script");
    
    
    
    SSRF에 취약성을 가지는 웹프락시를 만들어 보자.
    
    <%@ page import="java.io.*, java.net.*" %>
    <%
        String urlParam = request.getParameter("url");
        
        if (urlParam != null && !urlParam.isEmpty()) {
            try {
                URL url = new URL(urlParam);
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("GET");
                
                // 응답 읽기
                BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                String line;
                while ((line = reader.readLine()) != null) {
                    out.println(line);
                }
                reader.close();
                3050lp
            } catch (Exception e) {
                out.println("Error: " + e.getMessage());
            }
            return;
        }
    %>
    <html>
    <body>
        <h1>Simple Web Proxy</h1>
        <form method="get">
            URL: <input type="text" name="url" size="50">
            <input type="submit" value="Go">
        </form>
    </body>
    </html>