héroe bg sin separador
Directrices

대량 배정

지금부터는 Mass Assignment 취약점과 그 모습, 그리고 이를 방지하는 몇 가지 방법을 살펴보도록 하겠습니다.먼저 간단히 요약하면 다음과 같습니다.

대량 할당은 API 엔드포인트가 사용자가 수정할 수 있는 관련 객체의 속성을 제한하지 않는 취약점입니다.

이 취약점은 모델에 HTTP 매개변수를 자동으로 바인딩한 다음 검증 없이 계속 사용할 수 있도록 하는 라이브러리/프레임워크를 사용할 때 발생할 수 있습니다.

요청의 자동 바인딩을 객체에 사용하면 때때로 매우 유용할 수 있지만 모델에 사용자가 액세스할 수 없도록 되어 있지 않은 속성이 있는 경우 보안 문제가 발생할 수도 있습니다.

예시

사용자가 이름, 이메일 주소 등의 세부 정보를 변경할 수 있는 웹 페이지의 예를 사용하겠습니다.사용자 모델은 다음과 같이 정의되어 있습니다.

퍼블릭 클래스 사용자 모델 {

퍼블릭 롱 아이디 {get; set;}
공개 문자열 이름 {get; 세트;}
공개 문자열 암호 해시 {get; set;}
공개 문자열 이메일 주소 {get; set;}
퍼블릭 부울 isAdmin {get; set;}

}

프런트 엔드 부분은 다음과 같이 양식을 정의합니다.참고로 `isAdmin` 값이 없다는 점에 유의하세요.

<form method="POST">
<input name="Id" type="hidden">
<input name="Name" type="text">
<input name="EmailAddress" type="text">
<input type="submit">
</form>

컨트롤러는 다음과 같이 엔드포인트를 정의합니다.프레임워크는 'UserModel'을 매개변수로 사용하면 각 속성을 이 모델에 자동으로 매핑합니다.

[HTTP 포스트]
공개 부울 업데이트 사용자 (사용자 모델 모델)
{
//사용자가 자신만 업데이트하도록 보장
모델.ID = 요청.사용자.사용자 ID;

var 성공 = 사용자 서비스.UpdateUser (모델);

반환 성공;
}

여기서부터는 'UserService.UpdateUser' 메서드가 권한 부여 측면에서 더 이상의 검증을 수행하지 않고 제공된 사용자 객체만 저장한다고 가정할 수 있습니다.

(속성에 값을 제공하지 않으면 기존 값만 유지됩니다.)

즉, 사용자는 'isAdmin'을 사용하여 요청을 제출할 수 있습니다. 그러면 현재 값이 재정의되고 다음과 같이 사용자가 관리자로 지정됩니다.

<form method="POST">
<input name="Id" type="hidden" value="666">
<input name="Name" type="text" value="Bad guy">
<input name="EmailAddress" type="text" value="hacker@attacker.com">
<input name="IsAdmin" type="hidden" value="true">
<input type="submit">
</form>

완화 전략

다음은 대량 할당 취약점을 방지하기 위해 고려해야 할 몇 가지 완화 전략입니다.

요청 모델에 데이터 모델을 재사용하지 마십시오.

데이터 모델 (데이터베이스에 유지될 수 있음) 을 클라이언트와 통신할 때 사용되는 모델과 분리하는 것이 중요합니다.컨트롤러에 대한 양식 제출을 처리하는 것은 데이터베이스의 데이터 유지 및 데이터가 데이터베이스에 표시되는 방식과는 매우 다른 문제입니다.이로 인해 프런트엔드와 지속성 계층 간의 결합 수준이 좋았던 것보다 훨씬 더 높아집니다.

매핑에 명시적이어야 합니다.

자동 바인딩 (또는 매핑) 의 문제점은 명시적 매핑이 없기 때문에 모델에서 액세스할 수 없는 속성을 쉽게 노출할 수 있다는 것입니다.요청 모델과 나머지 백엔드 간의 매핑을 명시적으로 지정하면 처음부터 이러한 유형의 노출을 방지할 수 있습니다.

이는 요청과 데이터에 대해 서로 다른 모델을 사용하여 수행할 수 있습니다.그렇다고 해서 요청과 데이터 모델 간에 자동 매퍼를 사용할 수 있는 것은 아닙니다. 요청 모델이 특정 요청에 허용되지 않는 속성을 노출해서는 안 되기 때문입니다.

추가 예시

아래에는 이것이 어떻게 보일 수 있는지에 대한 몇 가지 추가 예시가 다른 언어로 제공됩니다.

C# - 안전하지 않음

공개 클래스 사용자 {
퍼블릭 롱 아이디 {get; set;}
공개 문자열 퍼스트네임 {get; 세트;}
공개 문자열 LastName {get; 세트;}
공개 문자열 암호 해시 {get; set;}
공개 문자열 국가 {get; 세트;}
공개 문자열 역할 {get; 세트;}
}

[HTTP 포스트]
공개 보기 결과 편집 (사용자 사용자)
{
//제공된 대로 사용자를 저장합니다.
사용자 서비스. 사용자 업데이트 (사용자)
반환 확인 ();
}

C# - 보안

공개 클래스 사용자 {
퍼블릭 롱 아이디 {get; set;}
공개 문자열 퍼스트네임 {get; 세트;}
공개 문자열 LastName {get; 세트;}
공개 문자열 암호 해시 {get; set;}
공개 문자열 국가 {get; 세트;}
공개 문자열 역할 {get; 세트;}
}
공개 클래스 업데이트 사용자 뷰 모델 {

공개 문자열 퍼스트네임 {get; 세트;}
공개 문자열 LastName {get; 세트;}
공개 문자열 국가 {get; 세트;}
}

[HTTP 포스트]
공개 보기 결과 편집 (사용자 뷰 모델 사용자 모델 업데이트)
{
변수 사용자 = 요청. 사용자;

사용자. 이름 = 사용자 모델. 이름;
사용자. 성 = 사용자 모델. 성;
사용자. 국가 = 사용자 모델. 국가;

사용자 서비스. 사용자 업데이트 (사용자)

반환 확인 ();
}

C# - 대안 - 매개 변수 제외

공개 클래스 사용자 {
퍼블릭 롱 아이디 {get; set;}
공개 문자열 퍼스트네임 {get; 세트;}
공개 문자열 LastName {get; 세트;}
공개 문자열 암호 해시 {get; set;}
공개 문자열 국가 {get; 세트;}
공개 문자열 역할 {get; 세트;}
}

[HTTP 포스트]
공개 보기 결과 편집 ([바인드 (포함 = “이름, 성, 국가”)] 사용자 사용자)
{
if (요청.사용자.ID!= 사용자 ID) {
반품 금지 (“다른 사용자의 변경 요청”)
}
var 기존 사용자 = 요청. 사용자;
사용자. 암호 해시 = 기존 사용자. 암호 해시;
사용자. 역할 = 기존 사용자. 역할;

사용자 서비스. 사용자 업데이트 (사용자)

반환 확인 ();
}

자바 - 안전하지 않음

공개 클래스 사용자 {
공개 정수 ID;
공개 문자열 이름;
공개 문자열 성 이름;
공개 문자열 암호 해시;
퍼블릭 스트링 국가;
공개 문자열 역할;
}

@RequestMapping (값 = “/UpdateUser”, 메서드 = 요청메서드.POST)
공개 문자열 업데이트 사용자 (사용자 사용자) {
사용자 서비스. 업데이트 (사용자)
“사용자 업데이트 페이지”를 반환합니다.
}


자바 - 시큐어

퍼블릭 클래스 유저뷰모델 {
공개 문자열 이름;
공개 문자열 성 이름;
퍼블릭 스트링 국가;
}
공개 클래스 사용자 {
공개 정수 ID;
공개 문자열 이름;
공개 문자열 성 이름;
공개 문자열 암호 해시;
퍼블릭 스트링 국가;
공개 문자열 역할;
}
@RequestMapping (값 = “/UpdateUser”, 메서드 = 요청메서드.POST)
공용 문자열 업데이트 사용자 (@AuthenticationPrincipal 사용자, 현재 사용자, 사용자 뷰 모델, 사용자 뷰 모델, 사용자 뷰 모델) {
현재 사용자. 이름 = 사용자 뷰 모델. 이름;
현재 사용자. 성 = 사용자 뷰 모델. 성;
현재 사용자. 국가 = 사용자 뷰 모델. 국가;

사용자 서비스. 업데이트 (현재 사용자)
“사용자 업데이트 페이지”를 반환합니다.
}

자바스크립트 - 안전하지 않음

app.get ('/사용자/업데이트', (요청, 해상도) => {
변수 사용자 = 필수 사용자;

객체 지정 (사용자, 필수 본문);

사용자 서비스. 업데이트 (사용자)

“사용자가 업데이트되었습니다.” 를 반환합니다.
})

자바스크립트 - 보안

app.get ('/사용자/업데이트', (요청, 해상도) => {
변수 사용자 = 필수 사용자;

사용자 이름 = 필수 본문.이름;
사용자. 성 = req.Body.LastName;
사용자 국가 = 필수 본문 국가;

사용자 서비스. 업데이트 (사용자)

“사용자가 업데이트되었습니다.” 를 반환합니다.
})

파이썬 - 안전하지 않음

@app .route (“/사용자/업데이트”, 메서드= ['POST'])
def 업데이트_사용자 ():

사용자 = 요청. 사용자
양식 = request.form.to_dict (플랫=참)

.items () 형식의 키, 값의 경우:
setattr (사용자, 키, 값)

사용자 서비스. 사용자 업데이트 (사용자)

리턴 리다이렉션 (“/user”, 코드=201)

파이썬 - 시큐어

@app .route (“/사용자/업데이트”, 메서드= ['POST'])
def 업데이트_사용자 ():

사용자 = 요청. 사용자
양식 = 요청 양식

사용자. 이름 = 양식. 이름
사용자. 성 = 형식. 성

사용자 서비스. 사용자 업데이트 (사용자)
리턴 리다이렉션 (“/user”, 코드=201)