crAPI靶场解
在线靶场http://crapi.apisec.ai/login
也可本地docker搭建
关卡列表在https://owasp.org/www-project-crapi/
我是本地搭建。先注册一个靶场账号,登录,访问8025端口,打开邮件把自己的车辆信息加入自己账号中(在127.0.0.1:8888/verify-vehicle接口)
添加自己的dashboard显示自己的车的信息:
Challenges
BOLA Vulnerabilities
Challenge 1 - Access details of another user’s vehicle
拿到其他人的车辆信息
访问Community页面
随便点一个评论同时抓包,截取响应包,把响应体信息暂存
{"id":"gCqDCgdSaVBCCeBF2VVa2V","title":"Title 3","content":"Hello world 3","author":{"nickname":"Robot","email":"robot001@example.com","vehicleid":"4bae9968-ec7f-4de3-a3a0-ba1b2ab5e5e5","profile_pic_url":"","created_at":"2025-07-13T02:43:17.7Z"},"comments":[],"authorid":3,"CreatedAt":"2025-07-13T02:43:17.7Z"}
返回dashboard
点击下面的刷新位置,同时抓包
把/identity/api/v2/vehicle/6aff27ea-c20f-44d5-b388-ade8b9ddc8ea/location这个接口的location那堆编码改成刚刚获取的vehicleid,也就是4bae9968-ec7f-4de3-a3a0-ba1b2ab5e5e5,然后重放
获得如下信息
{"carId":"4bae9968-ec7f-4de3-a3a0-ba1b2ab5e5e5","vehicleLocation":{"id":3,"latitude":"37.746880","longitude":"-84.301460"},"fullName":"Robot","email":"robot001@example.com"}
其中vehicleLocation字段是车的地点,算是敏感信息
Challenge 2 - Access mechanic reports of other users
这个挑战是关于访问其他用户提交的报告。
点击Contact Mechanic并抓包
第一个包没啥用
但后面还有一个类似的包,它的响应体是其他用户的报表的简陋信息
{"mechanics":[{"id":1,"mechanic_code":"TRAC_JHN","user":{"email":"jhon@example.com","number":""}},{"id":2,"mechanic_code":"TRAC_JME","user":{"email":"james@example.com","number":""}}],"previous_offset":null,"next_offset":null}
但是要获取的是详细信息,因此继续寻找切口。
我们自己提交一个报表并抓包
这里返回
{"response_from_mechanic_api":{"id":7,"sent":true,"report_link":"http://127.0.0.1:8888/workshop/api/mechanic/mechanic_report?report_id=7"},"status":200}
先访问这个http://127.0.0.1:8888/workshop/api/mechanic/mechanic_report?report_id=7
看看是什么
逆天没有jwt,我不是从这个网站跳转的吗为什么没有,那就自己加上去
可看到这次是详细的报表信息了,那么用自己的jwt能不能访问别人的报表呢,试试看
参数report_id改成1
可以,那就完成这关了,其他用户的只要改report_id即可
Broken User Authentication
Challenge 3 - Reset the password of a different user
挑战 3 - 重置其他用户的密码
官方提示:
- 在 crAPI 上查找其他用户的电子邮件地址
- 蛮力可能是答案。如果您遇到任何保护机制,请记住利用 REST API 的可预测特性来查找更多类似的 API 终端节点。
鼠标移到头像旁边的三角触发的那个按钮可以跳转reset-password接口http://127.0.0.1:8888/reset-password
,这个我试过了没用
请求包没啥信息,响应体也没用
但后来退出登录了一下,发现还有个forgot-password接口:
不出意外就是打这里了
刚刚第一关获取了其他用户的邮箱,这里就拿robot001@example.com这个用户来开刀
用自己邮箱重置密码看看,退出登录点忘记密码
去http://crapi.apisec.ai:8025/ 看验证码
官方说需要爆破,那这里发现是4位验证码,刚好就可以爆破
输入目标邮箱改密码,然后跳转到输入otp和新密码的界面抓包
添加payload爆破
一开始爆破发现全部是失败的,后来仔细分析一下官方提示,说“REST API 的可预测特性”,最后猜测可能是api版本可以修改,把路径的v3改成v2再爆破
成功了。这里提一下如果一开始先用v3版本api爆破再用v2,会导致两者都失败。因为otp是一次一密码的意思,0000-9999再v3版本用过了,就全部失效,。最后就算在v2的api爆破对了依然会返回500错误。也不知道多久重置,所以这边是重建了容器再打一次这关的
最后用刚刚改的密码!Ss12341234登录目标账号
这里登录后界面一片空白,不知道为什么,重建容器打一次依然是这样
只有抓包界面登录才看到效果
但是清除本地token存储和cookie后又能正常回到登录界面,继续做题
Excessive Data Exposure
Challenge 4 - Find an API endpoint that leaks sensitive information of other users
进入Community界面抓包,一个个包看,看到如下包
/community/api/v2/community/posts/recent?limit=30&offset=0这个接口就是所谓的敏感信息泄露接口。
Challenge 5 - Find an API endpoint that leaks an internal property of a video
个人中心上传一个视频
返回视频id,视频名字和conversion_params以及视频二进制流数据,一般来说id不能泄露。
Rate Limiting
Challenge 6 - Perform a layer 7 DoS using ‘contact mechanic’ feature
填一个报表,抓包"repeat_request_if_failed":改成true,"number_of_repeats"改成999999999999999999999999999
检测到你Dos了就得了
BFLA
Challenge 7 - Delete a video of another user
改视频名字同时抓包,方法改成options,删掉请求体
方法改成DELETE
{"message":"This is an admin function. Try to access the admin API","status":403}
说明/identity/api/v2/user/videos/52这个接口用delete方法访问就是删除视频的,但是要管理员
把路径中的user改成admin即可删除
Mass Assignment
Challenge 8 - Get an item for free
随便购买一个物品,然后退款抓包,响应包如下:
返回商店界面点击Past Orders按钮抓响应包得到:
{"orders":[{"id":6,"user":{"email":"123456789@qq.com","number":"18977513210"},"product":{"id":1,"name":"Seat","price":"10.00","image_url":"images/seat.svg"},"quantity":1,"status":"return pending","transaction_id":"312a961c-d8c1-46f1-b25b-90f1b5bead09","created_on":"2025-07-13T10:44:55.918691"}],"next_offset":null,"previous_offset":null,"count":1}
可以看到product的id是1,订单id是6
进入Past Orders界面,对购买的商品点击Order Details按钮,在商品详情界面(/orders?order_id=6)刷新再抓包,一个个看,抓到如下包
控制台源码搜索status状态看看有多少种,只搜到'delivered'和'Return'
Return是可以退货的状态,推测returned是退货完成状态
把刚刚那个包方法改成PUT,添加Content-Type: application/json,重放,payload如下
{"product_id":1,"quantity":1,"status":"returned"}
返回包显示状态已经修改
回界面看看
余额变回100了
Challenge 9 - Increase your balance by $1,000 or more
再买个椅子
根据上一关的方法抓到/workshop/api/shop/orders/{id}这个接口的包。添加payload:{"product_id":2,"quantity":10,"status":"delivered"}
和Content-Type: application/json请求头,方法改成PUT重发
回去看看是否生效,看到订单种商品数量是10个了
再回到订单列表界面(/past-orders)点击Return
用上一关的方法退货
发现余额多了100
后来发现关卡要求增加1000,那就把商品数量改成100来打一次这关即可
Challenge 10 - Update internal video properties
After solving the “Get an item for free” challenge, be creative and find a way to get refunded for an item you never returned, but this time try to get a bigger refund.
尝试找到允许您更改视频内部属性的接口。
第五关发现了视频有conversion_params属性
用改接口改conversion_params为-v codec h265,成功修改
SSRF
Challenge 11 - Make crAPI send an HTTP call to “www.google.com” and return the HTTP response.
这里测试ssrf,因为谷歌要FQ,较难看到效果,用本地起个python服务器代替了。
写一个报表提交
抓包改mechanic_api字段为服务器地址
终端这边是有反应不过是400。。。
NoSQL Injection
Challenge 12 - Find a way to get free coupons without knowing the coupon code.
这靶场后端用了mongdb,去问deepseek常见mongdb的NoSQL注入漏洞,得到一个{"$ne": ""}的payload,如下
上网搜了下文章,下面这样也得
使用后可以获得75元
问了下AI,如下回答
{"$ne": ""}
的原理
$ne
表示"不等于"(not equal)
- 当查询
{字段: {"$ne": ""}}
时,MongoDB会返回该字段不等于空字符串的所有文档 - 在MongoDB中:
- 如果字段存在且值不为空字符串,就符合条件
- 如果字段不存在,也会被包含(因为"不存在"当然不等于空字符串)
- 只有字段存在且值为空字符串的文档会被排除
所以它几乎会返回所有文档,除非有文档该字段明确设置为空字符串
{"$gt": ""}
的原理
$gt
表示"大于"(greater than)
- 当查询
{字段: {"$gt": ""}}
时,MongoDB会返回该字段值大于空字符串的所有文档 - 在MongoDB的字符串比较规则中:
- 任何非空字符串都"大于"空字符串
- 不存在的字段被视为"null",在比较中比空字符串"小"
所以它会返回:
- 所有该字段存在且值为非空字符串的文档
- 但会排除:字段不存在或值为空字符串的文档
- 为什么都能查"全部"?
在实际攻击中,它们都能返回大量数据的原因是:
$ne
的情况:
- 大多数文档要么字段有值(不为空),要么字段不存在
- 很少有文档会特地把字段设为空字符串
- 所以返回结果几乎是全部
$gt
的情况:
- 虽然理论上会排除字段不存在的情况
- 但在实际应用中,关键字段(如密码)通常都存在
- 所以对必填字段使用
{"$gt": ""}
也能返回几乎所有记录
- 简单示例
假设有个用户集合:
[
{username: "admin", password: "123456"}, // 有密码
{username: "guest", password: ""},// 密码为空
{username: "test"}// 无password字段
]
-
db.users.find({password: {"$ne": ""}})
会返回: -
admin用户(密码不为空)
-
test用户(无password字段,视为不等于空)
-
不返回guest用户(密码为空)
-
db.users.find({password: {"$gt": ""}})
会返回: -
只有admin用户(密码大于空字符串)
-
不返回guest(等于空)和test(无字段)
- 在攻击中的应用
在登录绕过攻击中:
- 攻击者提交
{"$ne": ""}
或{"$gt": ""}
作为密码 - 系统查询变成
db.users.find({username: "admin", password: {"$ne": ""}})
- 只要admin用户的密码不为空(通常都是这样),就能匹配成功
- 从而绕过密码验证
Challenge 13 - Find a way to redeem a coupon that you have already claimed by modifying the database
后端有postgresdb数据库,这是关系型数据库
输入正确兑换码抓包
第二个包如下
这里有sql注入{"coupon_code":"0'or '1'='1","amount":75}也能查到兑换码。
Challenge 14 - Find an endpoint that does not perform authentication checks for a user.
/workshop/api/shop/orders/x这个接口越权查看其他用户订单
Challenge 15 - Find a way to forge valid JWT Tokens
/dashboard界面抓包,直到出现如下的包
解码jwt得到
{"alg":"RS256"}.{"sub":"123456789@qq.com","iat":1752418223,"exp":1753023023,"role":"user"}.GBAzt9zSS3hXUYnSLMSYDpuYKnyu1m8t_HfpopmzxHzFLWGZFZdxUMuxbpf4U4twlnvXHVCh4Rd8bjvkluX2W-MOV50stWOjMB773c-rytZtBrzOPo-VPh2eKQN4z1epawoJrvfrwCdRhXL29eHOJJh5qftCrpyHJa0_6h2ZxcVqzKfgnPg30uOaLHzpiy3U1vzgHm1U0CnxNjd2qFl9-5x96ZcEE2jXPcH0njDq3ZD5WsajvPzF4lXQDxJL-uhiS5_X9W9lwzayD85N-pBV-_pUDWmNP9kPhSpvtEglEsG-qTS9Y8X2va0FX_N_vS3KzMJzVDdgITXSSGZEA_v6Gw
Header:
{"alg":"none"}
Payload: {"sub":"123456789@qq.com","iat":1752418223,"exp":1753023023,"role":"user"}
算法改成{"alg":"none"}
去除签名部分构造无验证算法jwt,如下:
eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjM0NTY3ODlAcXEuY29tIiwiaWF0IjoxNzUyNDE4MjIzLCJleHAiOjE3NTMwMjMwMjMsInJvbGUiOiJhZG1pbiJ9.
绕过成功
« 2 secret challenges »
There are two more secret challenges in crAPI, that are pretty complex, and for now we don’t share details about them, except the fact they are really cool.
/workshop/api/shop/products接口可以查看商品信息
改POST方法可添加商品
这里只发现1个,另一个没找到
方法总结
- ID替换越权:在请求中修改资源ID(如车辆ID、订单ID)尝试访问他人数据,例如将
/api/vehicle/自己ID/location
中的ID替换为他人ID。 - 水平越权探测:用普通账号访问相同层级的其他用户专属接口(如
/api/user/123→/api/user/456
)。 - 垂直越权探测:普通用户尝试访问管理员接口(如
/user/videos/
改为/admin/videos/
)。 - JWT算法篡改:将JWT头部
alg:RS256
改为alg:none
并删除签名绕过验证。 - JWT声明篡改:解码JWT后修改
role:user
为role:admin
提权。 - OTP爆破:对密码重置验证码接口进行4-6位数字爆破(需注意API版本差异,如
/v3/
改/v2/
)。 - 旧版本API探测:主动降级API路径版本号(如
/v3/reset
改为/v2/reset
)测试未修复漏洞。 - 批量分配攻击:添加未公开参数(如
status
、quantity
)覆盖服务端状态实现非法操作。 - 状态机篡改:修改订单状态(如
return pending
→returned
)绕过业务流程。 - 参数污染:篡改非用户输入参数(如视频的
conversion_params
)触发内部错误或未授权操作。有的时候可以把参数值设置为空看看。 - NoSQL注入:使用MongoDB运算符(如
{"$ne":""}
)绕过逻辑校验获取资源。 - SQL注入:经典Payload(
' or 1=1--
)测试字符串型参数。 - 枚举未授权端点:直接访问相似功能路径(如
/shop/orders/自己ID
→/shop/orders/他人ID
)。 - 错误信息挖掘:从500错误响应提取堆栈信息、数据库类型或敏感路径。
- 过度数据泄露分析:对比正常响应与业务需求,标记多余返回字段(如内部属性、他人邮箱)。
- SSRF触发点探测:查找接收URL参数的API(如报告提交接口),尝试指向外部服务(
http://attacker.com
)。 - 速率限制绕过:通过参数控制请求循环(如
repeat_request_if_failed=true&number_of_repeats=999999
)。 - 优惠券逻辑绕过:空值/永真条件(
0' or '1'='1
)兑换未知或已使用优惠码。 - HTTP方法篡改:GET请求改为POST/PUT/DELETE测试危险方法(如OPTIONS探测可用方法)。
- 业务流逆向:分析正常流程(购买→退款)并尝试跳过/逆转步骤(未发货直接退款)。
- 公开信息收集:从社区、错误页等接口收集邮箱、车辆ID等敏感信息用于攻击链。
- 自动化ID爆破:对低熵值ID(UUID、短数字)使用Intruder批量测试越权。
- 文件元数据篡改:上传文件时修改隐藏属性(如视频时长、转码状态)触发未预期行为。
- 响应对比分析:对比他人资源与自身资源的响应差异(如社区帖子接口暴露额外字段)。
- 接口方法拓展:对返回对象详情的GET接口,尝试POST/PUT方法创建/修改资源。
本文来自博客园,作者:积分别忘C,转载请注明原文链接:https://www.cnblogs.com/hackzz/p/18982290