Update samba_symlink_traversal to use RubySMB#21383
Conversation
ef48781 to
8c2b213
Compare
There was a problem hiding this comment.
Pull request overview
Switches the samba_symlink_traversal auxiliary module from the legacy Rex SMB1 client to the RubySMB backend by leveraging RubySMB’s UNIX symlink extension support, and updates dependencies accordingly.
Changes:
- Update
samba_symlink_traversalto connect withbackend: :ruby_smband create the symlink via RubySMB tree methods. - Add error handling for RubySMB
UnexpectedStatusCodein the module run path. - Move
ruby_smbdependency to a git-sourced branch and update the lockfile accordingly.
Impact Analysis:
- Blast radius: medium (framework-wide Ruby dependency change via Gemfile/Gemfile.lock; direct runtime impact primarily on SMB/RubySMB consumers).
- Data and contract effects: no schema changes; dependency source changes may affect runtime behavior/compatibility of RubySMB-based SMB operations.
- Rollback and test focus: rollback = revert Gemfile/Gemfile.lock + module change; validate SMB1 negotiation/login, share tree connect, and symlink creation on a Samba target with UNIX extensions enabled.
Reviewed changes
Copilot reviewed 2 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| modules/auxiliary/admin/smb/samba_symlink_traversal.rb | Uses RubySMB backend for SMB1 and calls the UNIX symlink operation via RubySMB. |
| lib/rex/proto/smb/simple_client.rb | Minor indentation fix in trans_pipe. |
| Gemfile.lock | Switches ruby_smb to a git source and updates transitive dependencies. |
| Gemfile | Adds git-sourced ruby_smb dependency reference. |
| print_status("Trying to mount writeable share '#{datastore['SMBSHARE']}'...") | ||
| simple.connect("\\\\#{rhost}\\#{datastore['SMBSHARE']}") | ||
|
|
||
| print_status("Trying to link '#{datastore['SMBTARGET']}' to the root filesystem...") | ||
| simple.client.symlink(datastore['SMBTARGET'], '../' * 10) | ||
| simple.client.last_tree.set_unix_link(symlink: datastore['SMBTARGET'], target: '../' * 10) |
There was a problem hiding this comment.
Important: Problem: simple.connect(...) doesn’t provide the Tree object and the next line relies on simple.client.last_tree, which is not a pattern used elsewhere (other RubySMB-using modules store the return value of tree_connect, e.g. modules/auxiliary/scanner/smb/smb_enumshares.rb:192 and smb_enum_gpp.rb:175). Impact: this adds a hidden dependency on RubySMB client state and can break if last_tree isn’t set to the intended share/tree. Fix: call tree = simple.client.tree_connect("\\\\#{rhost}\\#{datastore['SMBSHARE']}") and invoke tree.set_unix_link(...) on that explicit tree instance.
| gem 'timecop' | ||
| end | ||
|
|
||
| gem 'ruby_smb', git: 'https://github.com/zeroSteiner/ruby_smb', branch: 'feat/smb1/symlink' |
There was a problem hiding this comment.
Important: Problem: the Gemfile pins ruby_smb to a moving git branch on a fork (zeroSteiner/ruby_smb, feat/smb1/symlink). Impact: dependency supply-chain/reproducibility risk (a branch can be force-pushed or change without notice), which can make builds non-deterministic across environments. Fix: pin to a specific commit via ref: (and ideally the upstream repo or a released gem version) so the dependency is immutable.
| gem 'ruby_smb', git: 'https://github.com/zeroSteiner/ruby_smb', branch: 'feat/smb1/symlink' | |
| gem 'ruby_smb', git: 'https://github.com/zeroSteiner/ruby_smb', ref: 'REPLACE_WITH_VETTED_COMMIT_SHA' |
6c4b2dd to
54b8f32
Compare
jheysel-r7
left a comment
There was a problem hiding this comment.
Thanks for the PR @zeroSteiner. Testing looks great.
I'll land the ruby-smb side first. Could you please revert the Gemfile.lock changes here when you have a moment?
Testing
msf auxiliary(scanner/smb/smb_login) > set rhost 172.16.199.1
rhost => 172.16.199.1
msf auxiliary(scanner/smb/smb_login) > set rport 4455
rport => 4455
msf auxiliary(admin/smb/samba_symlink_traversal) > set smbuser smbuser
smbuser => smbuser
msf auxiliary(admin/smb/samba_symlink_traversal) > set smbpass smbuser
smbpass => smbuser
msf auxiliary(admin/smb/samba_symlink_traversal) > run
[*] Running module against 127.0.0.1
[*] 127.0.0.1:4455 - Connecting to the server...
[*] 127.0.0.1:4455 - Trying to mount writeable share 'tmp'...
[*] 127.0.0.1:4455 - Trying to link 'flag' to the root filesystem...
[*] 127.0.0.1:4455 - Now access the following share to browse the root filesystem:
[*] 127.0.0.1:4455 - \\127.0.0.1\tmp\flag\
[*] Auxiliary module execution completed
...
msfuser@msfuser-virtual-machine:~$ smbclient //172.16.199.1/tmp -p 4455 -U smbuser%smbuser -m NT1 --option='client min protocol = NT1' -c 'cd flag1; cd flag; ls'
cd \flag1\: NT_STATUS_OBJECT_NAME_NOT_FOUND
. D 0 Thu Jun 4 14:20:31 2026
.. D 0 Thu Jun 4 14:20:31 2026
run D 0 Thu Jun 4 14:07:45 2026
sbin D 0 Thu Jun 4 14:07:44 2026
lib64 D 0 Fri May 8 19:12:09 2026
home D 0 Thu Jun 4 14:07:49 2026
root D 0 Fri May 8 19:12:17 2026
etc D 0 Thu Jun 4 14:20:31 2026
libx32 D 0 Fri May 8 19:05:06 2026
lib D 0 Thu Jun 4 14:07:43 2026
var D 0 Fri May 8 19:12:17 2026
mnt D 0 Fri May 8 19:05:07 2026
lib32 D 0 Fri May 8 19:05:06 2026
dev D 0 Thu Jun 4 14:20:32 2026
srv D 0 Thu Jun 4 14:07:49 2026
tmp D 0 Thu Jun 4 14:07:44 2026
sys D 0 Thu Jun 4 14:20:32 2026
opt D 0 Fri May 8 19:05:07 2026
boot D 0 Mon Apr 18 03:28:59 2022
bin D 0 Thu Jun 4 14:07:44 2026
usr D 0 Fri May 8 19:05:07 2026
media D 0 Fri May 8 19:05:07 2026
proc D 0 Thu Jun 4 14:20:32 2026
.dockerenv H 0 Thu Jun 4 14:20:31 2026
407228088 blocks of size 1024. 129978512 blocks available
| def run | ||
| print_status('Connecting to the server...') | ||
| connect(versions: [1]) | ||
| connect(versions: [1], backend: :ruby_smb) |
There was a problem hiding this comment.
Just curious how far away are we from dropping the Rex client, is that being tracked somewhere? I assume we'll refactor out the backend: :ruby_smb once we're fully switched over?
There was a problem hiding this comment.
It's not being tracked anywhere that I'm aware of. The issue is that the remaining modules are all getting into edge cases such as this one where they either rely on functionality that RubySMB needs to have implemented or they're memory corruption vulnerabilities and we'd need likely need to test the exploits again on a live target to ensure that the RubySMB structures are equivalent to the Rex structures. My guess is this PR and the associated RubySMB PR are probably around 1/8th of the remaining work.
54b8f32 to
4e29704
Compare
Thanks @jheysel-r7, it should be all set now. |
| ruby-rc4 (0.1.5) | ||
| ruby2_keywords (0.0.5) | ||
| ruby_smb (3.3.19) | ||
| ruby_smb (3.3.20) |
There was a problem hiding this comment.
My apologies @zeroSteiner I just landed the RubySMB PR
3.3.21 was released today June 8th and should contain the changes from rapid7/ruby_smb#297: https://rubygems.org/gems/ruby_smb/versions/
| ruby_smb (3.3.20) | |
| ruby_smb (3.3.21) |
There was a problem hiding this comment.
Should be set now! Not sure why bundle update didn't catch it yesterday.
4e29704 to
196202c
Compare
Release NotesThis bumps Ruby SMB to version 3.1.21 and closes a feature gap between Ruby SMB and the Rex SMB client. With the feature gap closed, modules/auxiliary/admin/smb/samba_symlink_traversal.rb can now be switched from Rex to the RubySMB client. One less module in the way of dropping the ancient Rex client. |
This loads changes from rapid7/ruby_smb#297 and closes a feature gap between Ruby SMB and the Rex SMB client. With the feature gap closed,
modules/auxiliary/admin/smb/samba_symlink_traversal.rbcan now be switched from Rex to the RubySMB client. One less module in the way of dropping the ancient Rex client.Most of the logic is on the Ruby SMB side. The
samba_symlink_traversalmodule is the only one that appears to use the unix symlink extension in the framework, so it doesn't make sense IMHO to promote thesymlinkmethod to a first-class method on the SimpleClient instance. The abstraction provided by SimpleClient is also pretty irrelevant in this context because the module will only ever work with SMB1 anyways.Testing
Containerfile
smb.conf
Demo