Skip to content
isdnetworks
Go back

결과를 클라이언트가 정하고 있었다

랭킹에 이상한 점수가 올라왔다. 최대로 낼 수 있는 점수의 몇 배였다.

Table of contents

Open Table of contents

앱이 계산해서 보내고 있었다

스테이지가 끝나면 점수를 HTTPGET 쿼리에 담아 libcurl 로 보내고 있었다.

"%s/api/score?uid=%s&stage=%d&score=%d"

통신이 HTTPS 도 아니었다. 이 주소를 브라우저에 그대로 치면 아무 점수나 올릴 수 있다. 앱을 안 켜도 된다. 앱이 계산한 값을 서버가 그대로 받으면 그 값을 신뢰하는 것이다.

완전히 막을 수는 없었다

점수를 서버가 계산하려면 그 재료를 서버가 알아야 한다. 어떤 적을 몇 마리 잡았는지와 걸린 시간과 사용한 아이템과 콤보 횟수다.

이걸 다 보내면 요청이 커진다. 그 값들도 앱이 만드는 것이라 똑같이 조작할 수 있다. 완전히 막을 수 없다는 것이 먼저 정리됐다. 목표를 막는 것이 아니라 쉽게 못 하게 하고 이상한 것을 걸러내는 것으로 잡았다.

여러 겹을 뒀다

가장 간단한 것부터 했다. 서버가 스테이지별 최대 점수를 알고 있으니 그것을 넘으면 거부한다. 밸런스 표가 이미 서버에 있어서 계산할 수 있었고 이걸로 명백한 것들이 걸렸다.

시간도 봤다. 스테이지 시작을 서버에 알리고 끝날 때 그 사이 시간이 최소치보다 짧으면 거부한다. 시작을 안 보내고 끝만 보내는 것도 거부한다.

값에 HMAC-SHA1 서명을 붙이고 POST 로 바꿨다. 서버가 같은 방식으로 계산해 비교하니 비밀 값을 모르면 못 만든다. 다만 그 값이 .so 안에 있으니 strings 만 돌려도 후보가 나온다. 이것도 완전하지 않다. 각각은 뚫려도 다 뚫기는 번거롭게 만드는 것이 목표였다.

거부한 것의 기록과 문구

거부한 요청을 전부 JSON 한 줄로 남겼다. 같은 uid 가 반복해서 걸리면 그 계정을 본다.

[거부] uid=*** stage=5 score=999999 (최대 42000)
[거부] uid=*** stage=3 서명 불일치
[거부] uid=*** stage=7 시작 기록 없음

기록이 없으면 얼마나 시도되는지도 모른다. 넣고 나서 하루 수십 건이 걸린다는 것을 알았다.

사용자에게 보이는 문구는 정상 오류와 같게 했다. 조작이 감지됐다고 알리면 어떤 방법이 걸리는지를 알려 주는 셈이다.

정리


Share this post on:

Previous Post
무엇을 금지하는지 정확히 안 읽었다
Next Post
후보를 두 번에 걸쳐 좁혔다