LoginSignup
1
1

More than 5 years have passed since last update.

[Django REST Framework]CursorPaginationsのカーソルが無限ループする

Posted at

TL;DR

Paginationクラスの、offset_cutoffNoneを設定する(ドキュメントに書いてないような…?)

環境

django-rest-framework 3.9.4

現象

以下のCursorPaginationクラスを考える

class CursorPagination(pagination.CursorPagination):
  page_size_query_param = 'page_size'
  page_size = 100
  ordering = '-last_modified'

ViewSetのpagination_classにCursorPaginationを使ったViewからは、以下のようなレスポンスが返る。

{
  'next': 'http://hogehoge.com/api/?cursor=bz0xMTAwJnA9MjAwNS0wNy0yNCsxNiUzQTAxJTNBMTElMkIwMCUzQTAw',
  'data': ...
}

?cursor=以降は、データの部分集合を特定するための文字列(base64)。
nextのURLへのアクセスを繰り返すことで、すべてのデータを取得することが出来る。
しかし、last_modifiedが同じデータを1000件程取得すると、(リクエストURL)=(nextのURL)となる。
以降、無限ループに陥り、正しくデータが取得できない。
(対処法は、記事冒頭の通り)


問題は解決したのですが、もう少しだけソースコードを眺めてみました

# The offset in the cursor is used in situations where we have a
# nearly-unique index. (Eg millisecond precision creation timestamps)
# We guard against malicious users attempting to cause expensive database
# queries, by having a hard cap on the maximum possible size of the offset.

  • OFFSET操作は重いので、それを悪用した攻撃を防ぐための措置?
  • cursorのbase64文字列をデコード→URLデコードすると、o=1100&p=2005-07-24+16:01:11+00:00のようになる

    • oはオフセット、pは位置を示す(参考)
    • pはorderingに指定された属性を参照し、値が決まる
      • デフォルト値はcreated、上記の例ではlast_modified
    • pの値がnearly-uniqueであり、かつmax_page_num個以上あるときは、取得できたデータの個数分オフセットを進めたcursorが生成される
  • つまり、攻撃者は、o(オフセット)を過剰に大きく設定したcursorを作成して、リクエストを投げる?

    • 正常にデータが取得できるとき、expensive database queriesによる攻撃(DDos?)が成立する?
    • ので、対策としてオフセットの上限が1000に制限されている
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1