ASP.NET 개발 보안가이드

SQL Injection 취약점

취약점 상세 내용 및 보안 대책

웹 사이트는 DBMS와 연동하므로 웹 어플리케이션에서 사용자의 입력값을 통하여 데이타 트랜잭션이 발생함. 만일 사용자 입력값에 대한 유효성 검증이 누락될 경우, 악의적인 사용자는 정상적인 입력값 대신 SQL 쿼리문이 포함된 조작된 입력값을 전송하여 서버측 쿼리문의 구조를 변경할 수 있으며, 이를 통해서 사용자 인증을 우회하거나 데이터베이스의 정보를 유출시키고 서버의 시스템 명령어를 실행시킬 수도 있음.

보안 대책

입력제한

유형, 길이, 형식 및 범위 ASP.NET 응용 프로그램에 대한 모든 입력을 확인. 데이터 액세스 쿼리에 사용되는 입력을 제한함으로써, SQL 인젝션에서 응용 프로그램을 보호 할 수 있음.

  • 참조 : 입력을 제한하면 허용 문자의 목록을 작성하고, 목록에 없는 모든 문자를 거부하는 ​​정규식을 사용.

가. ASP.NET 웹 페이지 입력 제한 ASP.NET 웹 페이지에 대한 서버 측 코드를 입력을 제한함.

예) SSN 값을 ASP.NET에 의해 캡처하여 텍스트 상자의 입력을 제한

<%@ language="C#" %>
<form id="form1" runat="server">
    <asp:TextBox ID="SSN" runat="server"/>
    <asp:RegularExpressionValidator ID="regexpSSN" runat="server"         
                                    ErrorMessage="Incorrect SSN Number" 
                                    ControlToValidate="SSN"         
                                    ValidationExpression="^\d{3}-\d{2}-\d{4}$" />
</form>

예) SSN 입력이 같은 HTML 컨트롤과 같은 다른 소스, 쿼리 문자열 매개 변수 또는 쿠키의 경우에는 사용을 제한

if (Regex.IsMatch(Request.Cookies["SSN"], "^\d{3}-\d{2}-\d{4}$"))
{
    // access the database
}
else
{
    // handle the bad input
}

나. 데이터 액세스 코드 입력 제한
데이터 접근 코드의 유효성 검사를 제공

  • 신뢰할 수없는 클라이언트
    신뢰할 수없는 소스에서 데이터가 올 수있다 또는 데이터의 유효성을 검사하고 제한될 수 있으므로, 데이터 액세스 루틴에 대한 입력을 제한 유효성 검사 논리를 추가.
  • 라이브러리 코드
    데이터 액세스 코드가 여러 응용 프로그램에서 사용하도록 설계된 라이브러리로 패키지화되어 있으면 클라이언트 응용 프로그램에 대한 더 안전한 가정을 만들 수 있기 때문에, 데이터 액세스 코드가 자신의 유효성 검사를 수행.

예) 데이터 액세스 루틴 전에 SQL 문에서 매개 변수를 사용하여 정규 표현식을 사용하여 입력 매개 변수의 유효성을 검사하는 방법

using System;
using System.Text.RegularExpressions;

public void CreateNewUserAccount(string name, string password)
{
    // Check name contains only lower case or upper case letters, 
    // the apostrophe, a dot, or white space. Also check it is 
    // between 1 and 40 characters long
    if ( !Regex.IsMatch(userIDTxt.Text, @"^[a-zA-Z'./s]{1,40}$"))
      throw new FormatException("Invalid name format");

    // Check password contains at least one digit, one lower case 
    // letter, one uppercase letter, and is between 8 and 10 
    // characters long
    if ( !Regex.IsMatch(passwordTxt.Text, 
                      @"^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$" ))
      throw new FormatException("Invalid password format");

    // Perform data access logic (using type safe parameters)
    ...
}

저장 프로 시저의 매개 변수 사용

저장 프로 시저를 사용하면 반드시 SQL 인젝션을 방지하지 않음. 중요한 점은 저장 프로 시저 매개 변수를 사용. 이 매개 변수를 사용하지 않는 경우가 필터링되지 않은 입력을 사용하는 경우이며, 저장 프로 시저가 SQL 인젝션에 취약함.

예) 저장 프로 시저를 호출

using System.Data;
using System.Data.SqlClient;

using (SqlConnection connection = new SqlConnection(connectionString))
{
  DataSet userDataset = new DataSet();
  SqlDataAdapter myCommand = new SqlDataAdapter( 
             "LoginStoredProcedure", connection);
  myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
  myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11);
  myCommand.SelectCommand.Parameters["@au_id"].Value = SSN.Text;

  myCommand.Fill(userDataset);
}

가. 매개 변수가있는 저장 프로 시저 응용 프로그램의 사용 검토 매개 변수가있는 저장 프로 시저를 사용하여도 반드시 SQL 인젝션을 방지하지 않기 때문에, 저장 프로 시저의 응용 프로그램의 사용을 검토함.

예) 저장 프로 시저를 사용하는 응용 프로그램

CREATE PROCEDURE dbo.RunQuery
@var ntext
AS
        exec sp_executesql @var
GO

동적 SQL의 매개 변수 사용

저장 프로 시저를 사용할 수 없는 경우 동적 SQL 문을 생성 할 때, 매개 변수를 사용

예) 동적 SQL 매개 변수 사용

using System.Data;
using System.Data.SqlClient;

using (SqlConnection connection = new SqlConnection(connectionString))
{
  DataSet userDataset = new DataSet();
  SqlDataAdapter myDataAdapter = new SqlDataAdapter(
         "SELECT au_lname, au_fname FROM Authors WHERE au_id = @au_id", 
         connection);                
  myCommand.SelectCommand.Parameters.Add("@au_id", SqlDbType.VarChar, 11);
  myCommand.SelectCommand.Parameters["@au_id"].Value = SSN.Text;
  myDataAdapter.Fill(userDataset);
}

가. 일괄 처리 매개 변수 사용 SQL 텍스트 연결시 고유 한 매개 변수 이름을 사용하는 것을 확인하여 작업을 수행함.

예) 일괄 처리 매개 변수 사용

using System.Data;
using System.Data.SqlClient;
. . .
using (SqlConnection connection = new SqlConnection(connectionString))
{
  SqlDataAdapter dataAdapter = new SqlDataAdapter(
       "SELECT CustomerID INTO #Temp1 FROM Customers " +
       "WHERE CustomerID > @custIDParm; SELECT CompanyName FROM Customers " +
       "WHERE Country = @countryParm and CustomerID IN " +
       "(SELECT CustomerID FROM #Temp1);",
       connection);
  SqlParameter custIDParm = dataAdapter.SelectCommand.Parameters.Add(
                                          "@custIDParm", SqlDbType.NChar, 5);
  custIDParm.Value = customerID.Text;

  SqlParameter countryParm = dataAdapter.SelectCommand.Parameters.Add(
                                      "@countryParm", SqlDbType.NVarChar, 15);
  countryParm.Value = country.Text;

  connection.Open();
  DataSet dataSet = new DataSet();
  dataAdapter.Fill(dataSet);
}
. . .

XSS(Cross-Site Scripting) 취약점

취약점 상세 내용 및 보안 대책

Javascript, Vbscript 등 PC의 웹브라우저에서 실행되는 클라이언트 사이트 스크립트를 다른 사용자의 웹브라우저에서 실행하도록 함으로서 웹 브라우저를 제어하여 PC를 공격하는 취약점을 말함. 공격에 사용되는 유형은 크게 두가지로 분류.
(1) Reflected XSS
이 유형의 XSS는 클라이언트 측에서 전송된 데이터가 서버측에서 즉시 처리된 후 사용자에게 응답으로 전송되는 돌아오는 경우에 해당함. 다음과 같은 상황에서 발생이 가능함.

  • 검색 페이지에서 입력된 검색어가 검색 결과 페이지에 표시되는 경우
  • URL에 포함된 특정 파라미터가 응답 페이지에 hidden 속성으로 포함되어 있는 경우

위의 두가지 유형은 본질적으로는 동일한 내용임. 이러한 유형의 XSS 는 사회 공학적 방법 등을 통해서 공격 스크립트가 포함된 URL을 타인이 실행하도록 유도함으로서 (예를 들면 메신저로 링크를 전송하거나, 게시판에 URL을 링크로 걸어놓고 클릭을 유도하는 방식 등) 공격에 이용될 수 있음. 다만, 공격자가 강제적으로 타 사용자의 PC에서 공격 스크립트가 실행되도록 할 수는 없으므로 일반적으로 심각한 취약점으로 분류되지는 않음. 그러나 공공기관이나 금융권과 같이 신뢰도가 중요한 대상인 경우에는 이 유형의 XSS도 모두 제거해야 하며, 그 외에도 대외에 공개된 웹사이트라면 일반적으로 제거하도록 하는 것이 좋음.

(2) Stored XSS
게시판과 같이 사용자가 입력한 정보가 서버측 DB에 저장되어 있다가 타 사용자가 해당 정보를 열람할 때 DB에 저장되어 있는 정보를 가져와 화면에 출력하는 방식의 웹 애플리케이션에서 발생하는 취약점으로서, XSS의 가장 핵심적인 취약점이라고 할 수 있다. 불특정 다수를 대상으로 열람자의 웹브라우저에서 공격 스크립트를 실행할 수 있기 때문에 심각한 위협이 발생함. 공격자가 XSS를 목적으로 악성 스크립트를 삽입하여 둘 수 있는 위치는 비단 게시판의 글 본문 뿐만 아니라 제목, 작성자 이름, 날짜, 첨부파일 이름 등이 모두 가능하며, 또한 게시판 외에도 사용자 프로필의 닉네임, 주소, 전화번호, 등 사용자가 입력이 가능한 모든 데이터에 스크립트가 삽입될 수 있음.

  • 게시판 : 제목, 본문, 작성자, 날짜, 첨부파일 이름, 태그, 분류 등
  • 사용자 프로필 : 이름, 닉네임, 주소, 전화번호, 이메일, 직업, 기타 입력정보
  • 기타 사용자가 입력 가능한 모든 데이터

보안 대책

ASP.NET 요청 유효성 검사가 활성화되어 있는지 확인

기본적으로 요청 유효성 검사는 Machine.config 파일에서 사용할 수 있음. 그 유효성 검사가 현재 서버의 Machine.config 파일에서 활성화되고 응용 프로그램의 Web.config 파일에서 설정을 무시하지 않는지 확인함.

가. ASP.NET 유효성 검사가 활성화되어 있는지 확인하는 테스트 절차

1. 유효성 검사를 비활성화 하여 ASP.NET 페이지를 만들 수 있음. 이렇게 하려면 설정 ValidateRequest를 = “false”를 다음 코드 예제와 같이 설정.

<%@ Page Language="C#" ValidateRequest="false" %>
<html>
 <script runat="server">
  void btnSubmit_Click(Object sender, EventArgs e)
  {
    // If ValidateRequest is false, then 'hello' is displayed
    // If ValidateRequest is true, then ASP.NET returns an exception
    Response.Write(txtString.Text);
  }
 </script>
 <body>
  <form id="form1" runat="server">
    <asp:TextBox id="txtString" runat="server" 
                 Text="<script>alert('hello');</script>" />
    <asp:Button id="btnSubmit" runat="server"   
                OnClick="btnSubmit_Click" 
                Text="Submit" />
  </form>
 </body>
</html>

2. 페이지를 실행함. 그럴 경우 스크립트 때문에 메시지 상자에 txtString를 지나 브라우저에서 클라이언트 측 스크립트로 렌더링됨.
3. 설정값이 ValidateRequest = “true” 또는 제거 ValidateRequest의 페이지 속성을 다시 페이지로 이동함. 다음과 같은 오류 메시지가 표시되어 있는지 확인함.

A potentially dangerous Request.Form value was detected from the client (txtString="<script>alert('hello...").

HTML 출력을 생성 검토 ASP.NET 코드

예) HTML 출력을 생성 검토 ASP.NET 코드 예제

Response.Write
<% =

HTML 출력은 입력 매개 변수를 포함 여부 확인

출력은 입력 매개 변수가 포함되어 있는지 여부를 확인하고 디자인 및 당신의 페이지 코드를 분석함. 이러한 매개 변수는 다양한 소스로부터 올 수 있음.

예)일반적인 입력 소스

  • Form 필드
Response.Write(name.Text);
Response.Write(Request.Form["name"]);
Query Strings
Response.Write(Request.QueryString["name"]);
  • 쿼리문자열
Response.Write(Request.QueryString["username"]);
  • 데이터베이스 및 데이터 액세스 방법
SqlDataReader reader = cmd.ExecuteReader();
Response.Write(reader.GetString(1));
  • 쿠키 컬렉션
Response.Write(
Request.Cookies["name"].Values["name"]);
  • 세션 및 응용 프로그램 변수
Response.Write(Session["name"]);
Response.Write(Application["name"]);

잠재적으로 위험한 HTML 태그와 속성 검토

동적으로 HTML 태그를 생성하고 잠재적으로 안전하지 않은 입력으로 태그 속성을 구성하는 경우, 악의적인 사용자가 사용하기 전에 태그 속성을 HTML로 인코딩해야함. 예) aspx 페이지에서는 <사용하여 리턴 페이지에 HTML을 직접 작성하는 방법

<%@ Page Language="C#" AutoEventWireup="true"%>

<html>
  <form id="form1" runat="server">
    <div>
      Color:&nbsp;<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox><br />
      <asp:Button ID="Button1" runat="server" Text="Show color" 
         OnClick="Button1_Click" /><br />
      <asp:Literal ID="Literal1" runat="server"></asp:Literal>
    </div>
  </form>
</html>

<script runat="server">
  private void Page_Load(Object Src, EventArgs e)
  {
    protected void Button1_Click(object sender, EventArgs e)
    {
      Literal1.Text = @"<span style=""color:" 
        + Server.HtmlEncode(TextBox1.Text)
        + @""">Color example</span>";
    }           
  }
</Script>

가. 잠재적으로 위험한 HTML 태그

예)악의적인 사용자가 스크립트 코드를 삽입 할 수 있는 일반적으로 사용되는 HTML 태그

  • <applet>
  • <body>
  • <embed>
  • <frame>
  • <script>
  • <frameset>
  • <html>
  • <iframe>
  • <img>
  • <style>
  • <layer>
  • <link>
  • <ilayer>
  • <meta>
  • <object>


예) HTML 특성을 사용하여 SRC , lowsrc , 스타일 및 HREF 크로스 사이트 스크립팅을 삽입

<img src="javascript:alert('hello');">
<img src="java&#010;script:alert('hello');">
<img src="java&#X0A;script:alert('hello');">


예) 사용할 수있는 스타일을 다음과 같이 MIME 타입을 변경하여 스크립트를 삽입하는 태그

<style TYPE="text/javascript">
  alert('hello');
</style>

대책 평가

몇 가지 입력을 사용하여 HTML을 생성하는 ASP.NET 코드를 찾을 때, 특정 응용 프로그램에 대한 적절한 대책을 평가

가. HTML 출력을 인코딩
웹 페이지에 텍스트 출력을 작성하고 텍스트를 HTML 특수 문자 (예 : <,>, and & 포함 된 경우 모르는 경우 )를 사용하여 텍스트를 미리 처리
예) 텍스트 사용자 입력, 데이터베이스 또는 로컬 파일에서 온 경우이 작업을 수행

Response.Write(HttpUtility.HtmlEncode(Request.Form["name"]));

나. URL의 출력 인코딩
클라이언트 입력을 포함하는 URL 문자열을 반환
예) URL 문자열을 인코딩하는 방법

Response.Write(HttpUtility.UrlEncode(urlString));

다. 필터 사용자 입력
텍스트 입력 필드의 어떤 종류를 예를 들어 HTML 요소의 범위를 수락해야 페이지가 있다면, 페이지에 대해 ASP.NET 유효성 검사를 요청시 비활성화해야함. 이렇게 여러 페이지가있는 경우, 동의 할 경우에만 HTML 요소를 허용하는 필터를 만듬.

  • 안전 제한된 HTML 입력
  1. ASP.NET 유효성 검사를 요청시 비활성화 ValidateRequest=“false”로 하는 특성을 @ Page로 지시.
  2. 문자열 입력 인코딩 HtmlEncode 방법.
  3. 사용되는 StringBuilder을 불러오고 교체 선택적으로 허용 할 HTML 요소에 인코딩을 제거하는 방법.