본문 바로가기

소프트웨어/Node

[Node.js/보안] Express hpp 미들웨어

TLDR;

  • hpp : HTTP Parameter Pollution
  • Express의 중복 이름 파라메터 공격을 방어
  • 성능에 큰 무리가 안 가는 것으로 보임
  • hpp - npm




왜 글을 쓰는가..

 

최근 작업하던 Node.js WEB API 프로젝트에서 query parameter를 Array의 형태로 받도록 API를 만들었습니다.

개발 환경에서는 같은 이름의 query parameter로 여러개를 요청하면 Express에서는 Array의 형태로 req.query에서 parameter name을 key로 얻어 사용할 수 있었습니다.



<!-- 요청 API -->
https://localhost:9876/api/api1?key=data1&key=data2



// req.query
{
  key: ["data1", "data2"]
}




여기까지는 OK. 😀


이렇게 잘 됐다고 자화자찬 후 배포 서버에 올리기 전 테스트를 진행하다보니 여러개의 query parameter로 전달된 값들이 모두 무시되고 단 한개의 값만 담겨져 있었습니다. 🙀




// req.query
{
  key: "data2"
}






이래저래 삽질을 하다가 찾아보니 운영 환경에서만 hpp 모듈이 적용되었고, 이 때문에 여러개의 같은 이름을 가진 parameter 들이 마지막 한개를 제외하고 모두 무시 되었던 것이었죠.




hpp?

hpp는 HTTP Paramter Pollution의 약자입니다.

Express가 동일한 이름을 가진 파라메터가들이 있을 경우 Array로 만들어주는데 의도치 않은 동작을 하도록 외부에서 공격할 수 있는 보안 문제가 될 수 있다고 합니다.

입력 데이터 검증을 회피하거나 앱 크래시를 유발 시킬 수 있죠. 이를 방어하기 위한 express 모듈입니다.




설치방법

npm 명령어로 hpp를 설치합니다.

$ npm install hpp --save




Node.js의 메인 javascript에 다음과 같이 추가해줍니다.

// ...
var hpp = require('hpp');
 
// ...
app.use(bodyParser.urlencoded()); // 바디 파서는 미리 호출해야합니다.
app.use(hpp()); // <- 여기에 추가해주세요.
 
// 여기서부터 미들웨어를 추가하면 HTTP Parameter pollution을 방어할 수 있습니다~
app.get('/search', function (req, res, next) { /* ... */ });




req.query의 경우

기본적으로 req.query의 최상위 파라메터가 Array인지 체크합니다. 만약 파라메터가 Array라면 req.queryPolluted로 이동시켜버리고 req.query에는 Array의 가장 마지막 값을 할당합니다.

GET /search?firstname=John&firstname=Alice&lastname=Doe
        ->

req: {
    query: {
        firstname: 'Alice',
        lastname: 'Doe',
    },
    queryPolluted: {
        firstname: [ 'John', 'Alice' ]
    }
}

이 기능을 해제하고 싶을 때에는 다음과 같이 옵션을 추가해줍니다.app.use(hpp({ checkQuery: false }))




req.body의 경우

이 친구는 req.body의 최상위 파라메터를 Array인지 케츠합니다. 만약 파라메터가 Array라면 req.bodyPolluted로 이동시켜버리고 req.body에 Array의 가장 마지막 값을 할당합니다.

POST firstname=John&firstname=Alice&lastname=Doe
        ->
        
        req: {
            body: {
                firstname: 'Alice',
                lastname: 'Doe',
            },
            bodyPolluted: {
                firstname: [ 'John', 'Alice' ]
            }
        }

이 기능을 해제하고 싶을 때에는 다음과 같이 옵션을 추가해줍니다.app.use(hpp({ checkBody: false }))




특정 파라메터를 화이트리스트(White list)에 등록

특정 파라메터가 Array로서 전달되는 것을 허용하고자 한다면 아래와 같이 hpp middleware를 추가해줍니다.

app.use(hpp());

// key 파라메터는 이제 Array로 받을 수 있습니다.
app.use('/search', hpp({ whitelist: [ 'key' ] }));
      
GET /search?package=Helmet&package=HPP&filter=nodejs&filter=iojs
        ->
        
        req: {
            query: {
                package: 'HPP',
                filter:  [ 'nodejs', 'iojs' ], // queryPolluted가 되었지만 여전히 Array입니다.
            },
            queryPolluted: {
                package: [ 'Helmet', 'HPP' ]
            }
        }




성능에는 문제가 없능가?

개발자의 말로는 본인 맥북에서 요청 한번에 0.002ms가 소요되었다고 합니다... 😁




마치며..

의도치 않는 동작을 방지하기 위한 hpp 미들웨어지만 알고 쓰지 않으면 저와 같이 당황스러운 현상이 발생할 수 있습니다.

저와 같은 문제를 겪게 되었을 때 해결 시 도움이 된다면 좋겠습니다~




같이 보면 좋은 링크