In 2019, we published attacks on PDF Signatures and PDF Encryption. During our research and studying the related work, we discovered a lot of blog posts, talks, and papers focusing on malicious PDFs causing some damage. However, there was no systematic analysis of all possible dangerous features supported by PDFs, but only isolated exploits and attack concepts.
Our evaluation reveals 26 of 28 popular PDF processing applications are vulnerable to at least one attack. You can download all malicious PDFs here. You can also find more technical details in our NDSS'21 paper.
To identify attack vectors, we systematically surveyed which potentially dangerous features exist in the PDF specification. We created a comprehensive list with all PDF Actions that can be called. This list contains 18 different actions that we carefully studied.
We selected eight actions – the ones that directly or indirectly allow access to a file handle and may therefore be abused for dangerous features such as URL invocation or writing to files. Having a list of security-sensitive actions, we proceeded by investigating all objects and related events that can trigger these actions.
We identified four PDF objects which allow calling arbitrary actions (Page, Annotation, Field, and Catalog). Most objects offer multiple alternatives for this purpose. For example, the Catalog object, defines the OpenAction or additional actions (AA) events. Each event can launch any sequence of PDF actions, for example, Launch, Thread, etc. JavaScript actions can be embedded within documents. It opens a new area for attacks, for example, new annotations can be created that can have actions which once again lead to accessing file handles.
Denial of Service
The goal of the denial of service class of attacks is enforcing to process PDF applications in consuming all available resources (i.e., computing time or memory) or causes them to crash by opening a specially crafted PDF document. We identified two variants: Infinite Loop and Deflate Bomb.
Infinite Loop
This variant induces an endless loop causing the program execution to get stuck. The PDF standard allows various elements of the document structure to reference to themselves, or to other elements of the same type.
Action loop: PDF actions allow to specify a Next action to be performed, thereby resulting in "action cycles".
ObjStm loop: Object streams may extend other object streams allows the crafting of a document with cycles.
Outline loop: PDF documents may contain an outline. Its entries, however, can refer to themselves or each other.
Calculations: PDF defines "Type 4" calculator functions, for example, to transform colors. Processing hard-to-solve mathematical formulas may lead to high demands of CPU.
JavaScript: Finally, in case the PDF application processes scripts within documents, infinite loops can be induced.
Deflate Bomb
Data amplification attacks based on malicious zip archives are well-known. The first publicly documented DoS attack using a "zip bomb" was conducted in 1996 against a Fidonet BBS administrator. However, not only zip files but also stream objects within PDF documents can be compressed using various algorithms such as Deflate to reduce the overall file size.
Information Disclosure
The goal of this class of attacks is to track the usage of a document by silently invoking a connection to the attacker's server once the file is opened, or to leak PDF document form data, local files, or NTLM credentials to the attacker.
URL Invocation
PDF documents that silently "phone home" should be considered as privacy-invasive. They can be used, for example, to deanonymize reviewers, journalists, or activists behind a shared mailbox. The attack's goal is to open a backchannel to an attacker-controlled server once the PDF file is opened by the victim.
The possibility of malicious URI resolving in PDF documents has been introduced by Hamon [1] who gave an evaluation for URI and SubmitForm actions in Acrobat Reader. We extend their analysis to all standard PDF features that allow opening a URL, such as ImportData, Launch, GoToR, and JavaScript.
Form Data Leakage
Documents can contain forms to be filled out by the user – a feature introduced with PDF version 1.2 in 1996 and used on a daily basis for routine offices tasks, such as travel authorization or vacation requests. The idea of this attack is as follows: The victim downloads a form – a PDF document which contains form fields – from an attacker controlled source and fills it out on the screen, for example, in order to print it. The form is manipulated by the attacker in such a way that it silently send input data to the attacker's server.
Local File Leakage
The PDF standard defines various methods to embed external files into a document or otherwise access files on the host's file system, as documented below.
External streams: Documents can contain stream objects (e.g., images) to be included from external files on disk.
Reference XObjects: This feature allows a document to import content from another (external) PDF document.
Open Prepress Interface: Before printing a document, local files can be defined as low-resolution placeholders.
Forms Data Format (FDF): Interactive form data can be stored in, and auto-imported from, external FDF files.
JavaScript functions: The Adobe JavaScript reference enables documents to read data from or import local files.
If a malicious document managed to firstly read files from the victim's disk and secondly, send them back to the attacker, such behavior would arguably be critical.
Credential Theft
In 1997, Aaron Spangler posted a vulnerability in Windows NT on the Bugtraq mailing list [2]: Any client program can trigger a connection to a rogue SMB server. If the server requests authentication, Windows will automatically try to log in with a hash of the user's credentials. Such captured NTLM hashes allow for efficient offline cracking and can be re-used by applying pass-the-hash or relay attacks to authenticate under the user's identity. In April 2018, Check Point Research [3] showed that similar attacks can be performed with malicious PDF files. They found that the target of GoToR and GoToE actions can be set to \\attacker.com\dummyfile, thereby leaking credentials in the form of NTLM hashes.
Data Manipulation
This attack class deals with the capabilities of malicious documents to silently modify form data, to write to local files on the host's file system, or to show a different content based on the application that is used to open the document.
Form Modification
The idea of this attack is as follows: Similar to Form Data Leakage attacks, the victim obtains a harmlessly looking PDF document from an attacker controlled source, for example, a remittance slip or a tax form. The goal of the attacker is to dynamically, and without knowledge of the victim, manipulate form field data.
File Write Access
The PDF standard enables documents to submit form data to external webservers. Technically the webserver's URL is defined using a PDF File Specification. This ambiguity in the standard may be interpreted by implementations in such a way that they enable documents to submit PDF form data to a local file, thereby writing to this file.
Content Masking
The goal of this attack is to craft a document that renders differently, depending on the applied PDF interpreter. This can be used, for example, to show different content to different reviewers, to trick content filters (AI-based machines as well as human content moderators), plagiarism detection software, or search engines, which index a different text than the one shown to users when opening the document.
Stream confusion: It is unclear how content streams are parsed if their Length value does not match the offset of the endstream marker, or if syntax errors are introduced.
Object confusion: An object can overlay another object. The second object may not be processed if it has a duplicate object number, if it is not listed in the XRef table, or if other structural syntax errors are introduced.
Document confusion: A PDF file can contain yet another document (e.g., as embedded file), multiple XRef tables, etc., which results in ambiguities on the structural level.
PDF confusion: Objects before the PDF header or after an EOF marker may be processed by implementations, introducing ambiguities in the outer document structure.
Code Execution
The goal of this attack is to execute attacker-controlled code. This can be achieved by silently launching an executable file, embedded within the document, to infect the host with malware. The PDF specification defines the Launch action, which allows documents to launch arbitrary applications. The file to be launched can either be specified by a local path, a network share, a URL, or a file embedded within the PDF document itself.
Evaluation
Out of 28 tested applications, 26 are vulnerable to at least one attack.
In this chapter we will take a look at bypassing UI restrictions using Indirect Object Reference (IDOR) vulnerabilities to bypass unprotected functionality. We will then take a look at various authorization schemes and how to implement them so you can easily spot authorization issues when attacking contracts. We will take a look at both simple authorization and role-based authorization.
Understanding Smart Contract Authorization and Visibility
Smart contracts function in much the same way as an API that uses endpoints as interfaces to its functionality. You can code DApps for various platforms and access needed functionality within smart contracts for value transfers with functional logic. A common issue in the past was that smart contract functions had public visibility by default, meaning that they were accessible by anyone knew how to interact with them. If you didn't explicitly define the access level of the function it would automatically default to public, allowing anyone to call the function and perform actions using the contracts ABI.
In newer versions of solidity, the compiler will complain and refuse to compile if you do not explicitly define the visibility of a function as one of the following:
üExternal – Is accessible to other contracts but cannot be accessed internally to the contract.
üPublic - Is accessible to other contracts and can be accessed internally.
üInternal – Can only be accessed within the current contract or contracts deriving from it
üPrivate – These are only visible by the contract that defined them.
A quick example of a pubic vs a private method is as follows:
Action Steps:
üOpen up remix in your browser
üCreate a new solidity file named visibility.sol
üType the following code into the new document and compile/deploy the contract.
üPlay with the resulting functionality taking note of the visibility definitions above.
Simple Visibility Example:
1.pragma solidity ^0.6.6;
2.
3.contract visibility {
4.
5.functionadd(uint _a, uint _b) private pure returns (uint){
6.return _a + _b;
7.}
8.
9.function get_add_result(uint a, uint b) public pure returns (uint){
10.returnadd(a, b);
11.}
12.}
The visibility.sol contract has two functions at lines 5 and 9. The add function at line 9 is set to private which means that you cannot call it directly from an external call with the contracts ABI, nor with another contract using an external interface to this contract. However, it is called via another function within the same contract at line 10. This is because a function can call private functions within its own contract. Visibility limits certain functions you can call directly.
If we take a look at a screenshot of the deployed contract you will see that you only have a button to call the public function get_add_result and not the private add function. Note when submitting of 3 + 4 the get_add_result function is easily able to access the private functionality even if you cannot directly and 7 is returned.
Visibility is the first part of the equation and determines where the function is accessible from. There is also the matter of actual authorization to access functionality within the smart contract regardless of its visibility.This is not something that is built in by default and usually managed by the reviewing the address of the caller and making a decision.The address of the caller is generally going to be the msg.sender unless coded in alternative ways. We will use those other ways in upcoming chapters to bypass authorization in unique ways but for now we will focus on msg.sender.
Our functions are properly using private and public variables where appropriate, call it a day we are good to go right?Nope not even close, this just means we have a proper flow to our program and we have limited the visibility of functions that have no need to have direct interaction with a user.This does not stop a malicious hacker from directly accessing all of our public functions. Many of these public functions are bound to have sensitive functionality tied to financial transactions or interact with private functions that have the functionality you are trying to manipulate.
In a smart contract we need a way to actually tell who has access to a public function in order to setup authorized transactions, for example a bank transfer. Otherwise you would create an account and everyone would be able to access its funds and transfer the funds out to themselves. An attacker can call any public function within the contract, even those meant for administrators only.
Some examples of administrative functionality you would not want exposed would be a self-destruct function to render a contract useless or adding a new administrative account that does have authorization to sensitive functions.
To illustrate this point let's take a look at the following contract that has a few sensitive functions but no protection against unauthorized users. Before you read what the code below does, try the following steps and take a guess at what it's doing yourself and where it should have protections.
Action Steps:
üOpen your browser and go to remix.ethereum.org
üCreate a new file named noAuth.sol and type in the following code
üDeploy this contract and play with its deposit and withdraw functionality
üDo you see any potential issues in authorization?
üDo you see any potential issues with the business logic, etc?
10.function withdraw(uint amount) public payable {
11.msg.sender.transfer(amount);
12.}
13.
14.function kill() public {
15.selfdestruct(msg.sender);
16.}
17.}
The noAuth contract above is setup like a mini bank account, where you have the ability to deposit your funds and withdraw your funds. The funds are mapped to your msg.sender address on line 4.However, there are a few flaws with the way this contract is setup, both in authorization as well as business logic.
Let's go through the code and look about how it is setup.First, we have a deposit function on line 6 which accepts a value transfer via the "payable" keyword and applies the value to your current balance associated with your address.This function seems ok.
Next, we have a withdraw function which receives an amount and transfers that amount to the address which calls the function. But.
üThe withdraw function never actually checks if you have a balance associated with your address
üIt also doesn't validate if you have enough in your balance to send the amount you're asking for.
That poses a few interesting questions:
Where is this function withdrawing funds from if you don't have a balance associated with your address?
Can you simply liquidate the funds from the account as a whole?
Is this a potential business logic / authorization issue?
Finally, we have a kill function on line 14, which simply calls the built-in solidity self-destruct function and transfers all of the contract's funds to the caller of the function. This function will terminate the contracts functionality permanently and liquidate the contracts funds into the account address which ran the kill function. Much like the other two functions the kill function has no authorization, poses a risk to everyone's funds, and leaves the whole contract vulnerable to termination.
Let's play around with this functionality and determine if this is true within the Remix UI.
Action Steps:
üDeposit 10 Ether via the deposit function with the value field using account one.
üSwitch accounts to account two which has no funds and try to withdraw funds. Did it work?
üNow call the kill function from account two. What happened?
üTry to withdraw funds again with either account. What happened?
There are multiple critical issues with the above smart contract:
üIt's not validating the logic that users need to have funds associated with their account to make withdrawals.
üIt's not stopping a user from killing the contract and liquidating all of the funds of other accounts.
But I have UI mitigation's!!
What if a developer mitigates the issues via a Web or Mobile DApp simply by not providing a way for a user to execute the Kill functionality unless that user is the administrator in the DApp.Also, what if the UI manages your funds on the DApp's business logic. For example, restricting you from withdrawing funds if the address using the DApp does not have an appropriate balance.So, we are safe right?
No, not really, much like an API we can call these directly without ever accessing the UI.By directly calling the public functions of the smart contract, we do not have UI or middleware restrictions. In the web app world this would be equivalent to Indirect object reference (IDOR).You often see this with video games or web applications where the application from the front end looks good with solid restrictions. But then you start doing some enumeration you realize that all of the functionality comes from an API.
If you start poking around that API enumerating endpoints and fuzzing keywords you often will start finding API endpoints with interesting names that do things intended only for developers and administrators. This can lead to sensitive information disclosure or the ability to change and modify sensitive data. This is a very typical occurrence in web applications and Smart Contracts are no different.
For example, I was performing a penetration test against a large video game development shop whose primary fear was the ability to bypass the in-app purchases functionality.
I first started playing the video game and getting a feel for the game play and sequence of events. For example, the gameplay, how money transfers worked and how in-app purchases were processed. Everything seemed pretty good from the perspective of the mobile and web application UI parameters.I noted all of the calls were to external APIs and decided to take a look at those.
I setup both a local TCP sniffer on the mobile application, a TCP proxy and captured all of the web requests using a web proxy while playing the game.When reviewing the output, I noticed some interesting calls which exposed a list of every API endpoint in the application.
I started looking at the returned API endpoints and noted many functions which were not available to me from within the mobile application. Most notably for the client was functions named something similar to Get_Gold, and Get_All_Items. These endpoint names seemed interesting to me so I coded up a python loop which called the API for Get_Gold 100 times. At this point my Gold within the game increased 100-fold. Next, I called the Get_All_items endpoint and received every single item in the game for free.
At this point I didn't even need the gold which I just stole as I owned every single item in the game.Apparently, these were created by developers and never removed from the API endpoints. Instead they were just restricted by not having the functionality available on the UI of the game.
Yes, sometimes it is just that easy!!!But how do we do this with a smart contract?
So how does this story relate to your Smart Contracts?Well we have a few options available to us when trying to enumerate public functionality so we can make direct calls.The most useful resources for enumerating these issues is both the sour ce code and the Application Binary Interface (ABI).
First, we can take a look at the source code, if you are performing the penetration test the client should provide the source code. If the client does not provide the source code, most Ethereum projects tend to be open source, so you should find a GitHub with the source code. A third option for retrieving the source code would be pulling it from etherscan.io at the address where the contract is deployed. This should be located under the contract tab.For example, try the following steps to illustrate this point:
Go to etherscan.io and type chainlink into the search field at the top right and click the result shown below that pops up while your typing:
Next under the profile summary click the contract address:
You will then see a contract tab on the page that loads. Click that:
4.This will provide the source code for the application if it's available and it will provide the ABI:
ol
Secondly you will want the ABI for the contract in order to interact with it. The ABI is a JSON file which describes the functionality of the smart contract and how to interact with its functions.You can also generally obtain this exactly as you did above from the contract tab of etherscan.io shown below.
Another option if you were provided a contract from the client is to deploy a contract to Remix and grab the ABI that is created. You can grab this in Remix under the compiler section under compiler details. Just click the ABI text and it will copy it to your clipboard.
An ABI file for our noAuth contract will look something like the following Snippet.
Notice that the ABI above is simply just a JSON file that describes the functions in the contract for example the last function in the ABI shows the withdraw function with the following elements:
üIt takes an amount with the type uint256
üIt says it has no outputs
üIt is payable meaning it can send and receive transactions
üIt also notes that it is a function
So, the question is, how we can call these public functions directly if they were not programmed into the UI? The answer is we can use Web3 and programmatically interact with the contract via its ABI to bypass any front-end restrictions.
Let's directly interact with the noAuth contract and then let's implement authorization and requirement checks. This way you understand how to access public functions but also ways to properly prevent authorization issues with standard security libraries. This also helps with knowing what to look for when reviewing contract source code.
(Follow the video in the below reference section if you want a walkthrough of the setup)
1.Open up your browser, and in Remix and create the noAuth.sol file
2.Start Ganache-Cli on in your terminal
3.Set the provider in Remix Deploy section to Web3 Provider
4.Deploy the noAuth.sol contract, which will now deploy to your local ganache blockchain
5.Copy the address for noAuth.sol. You will need it.
6.Copy the address of the second account
7.Deposit 10 Ether via the Deposit function and the Value field (don't forget to change the value type to Ether from Wei)
Since not all of the public functions are accessible or may contain restrictions from our UI, we will attack the contract from the command line by directly calling the functions via Web3 using the contracts ABI.
We will need the ABI for this and we can get the ABI by going to the compilation section in Remix and clicking the ABI link shown below.
Note that as Web3 updates and ABI contract formats update you will need to update your web3 commands, I have had this happen to me frequently as this is a newer technology and the formats are always updating so, if this gives you issues feel free to steal the ABI from above to work with the Web3 commands below.
Now open up a terminal and install web3 followed by opening a node terminal:
$ npm install web3
$ node
Once node is running you will see a blank line with a > meaning you are in the node interactive console.We will now setup a direct connection and attack both the withdraw and kill functions to liquidate the contracts funds and terminate its functionality.The first thing we will need to do is setup our web3 import using the localhost target where our ganache-cli is running our blockchain transactions.Note with the commands below the output will usually say "undefined", you can ignore this output.
> const Web3 = require('web3')
> const URL = "http://localhost:8545"
> const web3 = new Web3(URL)
These lines of input simply create an instance of web3 and set its target network URL. If this were a bug bounty or pentest on another network you would supply that target URL for the target network, we can do this with Infura URL's to the test nets and mainnet on ethereum. We cover how to do this in other labs, but for this lab we are using our local targets.
Next lets setup our accounts so that we are using the 2nd account we selected in our remix account dropdown which was imported from ganache-cli. Note accounts start with 0 so the second account is actually labeled as account 1. And also note we deployed our contract with account 0.
> accounts = web3.eth.getAccounts();
> var account;
> accounts.then((v) => {(this.account = v[1])})
We setup our account in web3 simply by grabbing all of the accounts and then setting the value of account (singular) to 1 with the commands above. Syntax in node / JavaScript is a bit cryptic at times so the commands may look a bit odd but you can easily look them up in the web3 documentation.
Now we need to setup our target contract address from the proxy contract. We also need to paste in the full ABI and then connect the address and the ABI with a contract variable to reference in our calls to the contract. We can do that with the input below.
> const address = "ADD CONTRACT ADDRESS HERE"
> const abi = ADD ABI HERE
> const contract = new web3.eth.Contract(abi, address)
Now we are ready to make a call to the contract with the contract connection variable we just created. We will first withdraw funds to our second account which never deposited any funds. We do this using the command below that calls the withdraw function using our account variable. We also specify sending a default gas value since we need to send gas with transactions that make changes on the blockchain.
Before using the command below, first note your account balance in remix on your second account. This should be 100 ether at this point as it was not used in any transactions and it also holds no balance to withdraw in the contract.Then send the following command which requests 1 ether in Wei. Wei is denominated as the following 1 Ether = 1,000,000,000,000,000,000 Wei (10^18)
After a few moments you should see your balance increase in the second account on Remix.Now let's kill the contract so no one else can use it which will additionally send the remaining ether in the contract to our address per the msg.sender value in the source code call to self-destruct.
So obviously it's easy to understand we have functions we don't want directly called. To prevent this we need to implement some kind of protection scheme. Whether that is a require statements for accounts or more elaborate role-based designs.There are various ways we can implement authorization. We will cover a few common things you will see while auditing solidity smart contract code.While this is not a book about how to securely code your applications, in this case it is appropriate to understand what you might see while analyzing a contract you are trying to exploit.
The first example we will review is a simple authorization scheme using a contract owner and require statements.
Important Reminder:
Make sure to type out each of these contracts and test what they are doing for yourself before reading the descriptions below the code. The muscle memory of typing all of this code and trying to understand what you typed out will help you in spotting issues when you are auditing code. Also learning how to code will help you write exploits against contracts quickly and understand when it is or is not working and how to fix it.
15.function withdraw(uint amount) public payable {
16.require (balances[msg.sender] >= amount);
17.msg.sender.transfer(amount);
18.}
19.
20.function kill() public {
21.require(msg.sender == owner);
22.selfdestruct(msg.sender);
23.}
24. }
You will notice two changes to this contract from the original. The first change is on line 7 where a constructor sets the owner of the contract to the address of the user who deployed the contract.This constructor is only run one time when the contract is deployed. Meaning the owner cannot change.You will notice the initialization of the owner variable was also added on line 4.
The second change is the usage of require statements on lines 16 and 21. The require statement on line 16 is not associated to the owner but does add a check to make sure the user requesting a withdrawal has an amount in their balances mapping which is higher than the balance they are requesting to withdraw. This fixes the issue with users withdrawing funds they do not actually have.
The next require statement on line 21 makes sure to check that the user calling the Self-Destruct functionality is the owner of the contract. This prevents anyone from just killing the contract and stealing the funds from the account.
Something still smells bad regarding this contract!! The kill function is highly suspect as it removes all of the funds in the contract and could be indicative of an "exit scheme". Whereby a malicious developer creates a contract that handles funds, for example in a game, or an online exchange. But the malicious contract is created for the sole purpose of exiting with all of the user's funds when the balance reaches a desired balance.
These types of issues are something you should always take note of when you see them, and flag them during your assessment. The client might not like that you flagged their intended functionality but that is not your problem. They should know better than to have sketchy functionality and it should be called out.Even if they did not intend to use the function maliciously, it opens the door for someone else to do so.
Another popular authorization pattern is using an onlyOwner modifier. This is often coupled with Openzeppelin security libraries, which we will take a look at in our role-based example. However, in the example below we use a modifier in a simple way to illustrate what you may see in a contract.
19.function withdraw(uint amount) public payable {
20.require (balances[msg.sender] >= amount);
21.msg.sender.transfer(amount);
22.}
23.
24.function kill() publiconlyOwner{
25.selfdestruct(msg.sender);
26.}
27.}
This contract is also very similar to the simpleAuth contract above with a few small modifications to make it more extendable when there are a ton of functions that need authorization restrictions. These changes will also make the authorization simpler and more readable within your code.Changes in this contract are on lines 10 and 24.
On line 10 we define a modifier named onlyOwner which we can apply to any function. This modifier code will run prior to the original functions execution. In this example the modifier simply checks that the user calling the function is the owner of the contract. You will also note the use of _; which simply signals contract to continue running the function after this modifier code is finished.
You can apply this onlyOwner modifier to any function you wish to have authorization restrictions by simply adding onlyOwner in the function definition. You will see this on line 24. If modifiers requirement is not met the function will not be run. If the requirement is met it transfers control back to the function to continue execution.
WalkThrough of Fixing Authorization Issues With Modifiers:
Example Using Openzeppelin for Role Based Access Control:
The best way to cover your security needs as always is with well-audited, open source security libraries. One option we have for a bit more complex authorization is the Openzeppelin libraries located at:
For the previous examples you could have replicated the simple authorization with the ownable contract by OpenZeppelin by importing its functionality in the same way you would import library functionality in any other language.
Since we already looked at a simple example without OpenZeppelin, lets instead take a look at role-based authorization using OpenZeppelin. Role based authorization a bit more involved, but not complicated.Let's take a look at a simple example.
Before you read the descriptions type out the role-based code below in remix and try to figure out what's happening on your own by deploying this contract and playing with its functionality and see if you can understand how it works.
Action Steps to deploy:
üOpen up remix in your browser
üType out the following code and the import will import all of the OpenZeppelin files in a directory within remix automatically
üWith your first account, make sure to compile this with the newest version of Solidity that OpenZepplin files are using at the time of writing this was 0.6.2. I used version 0.6.6 without any issues. If versions change in the future you will get an error. Review the error and update the compiler version and pragma version in the code appropriately. But always use the latest version of OpenZepplin files.
üTake a look at the created users and make assumptions as to what each user has access to
üPlay with each function under both the admin and the user context with the first account and another account of your choice.
21.function withdraw(uint amount) public payable {
22.require(hasRole(user, msg.sender), "Not a user of this bank");
23.require (balances[msg.sender] >= amount);
24.
25.msg.sender.transfer(amount);
26.}
27.
28.function kill() public {
29.require(hasRole(admin, msg.sender), "Not an administrator");
30.selfdestruct(msg.sender);
31.}
32.}
Once you have the roleBased contract deployed you will notice a few changes from the simpleAuth version. First, we are importing the OpenZeppelin libraries which imports all of the prerequisite needs for the role-based access control into Remix.
Secondly, on lines 6-7 we are creating both a user and admin role identifiers. If you take a look at the documentation link from the references at the end of this section it states that the role identifier must be created as a bytes32 hash. We create these as a bytes32 type and hash them with keccak256 which is essentially the equivalent of a sha3 hash function. This type of hashing is standard on Ethereum's consensus engine for producing blocks. Keccak256 is often seen as the hashing function within Solidity smart contracts.
The constructor was updated to execute the _setupRole function from OpenZeppelin. This sets the admin user as the user who initially deployed the contract. In this case we used our first account, so our first account is our admin user.
The user account is then setup within the deposit function on line 16 for every user who deposits funds and is not already an administrator, as we don't want to overwrite the admin role with the user role. This would be a business logic error that eliminated all admin accounts, which would be bad.When you deposit funds as the second account your address will be associated with a regular user role.
As an example of how authorization is handled with role identifiers take a look at lines 22 and 29.On line 22 if you have not already deposited funds you will not have a user role so you cannot withdraw funds. You will be given an error when checking the hasRole requirement.
Try this out with a user who has not deposited funds yet.
Finally, within the kill function on line 29 you will see a check for an admin role identifier. If the account address calling kill does not have this associated role identifier, an error is displayed and the transaction will not process.
Try the kill function with your second user and take a look at your output window. It should turn red and show that error.Now if you switch back to your admin user on the first account you can successfully kill the contract.
Note that you can also enumerate, grant and revoke user roles. Check out the references section below for more information if you are interested in that functionality.
I hope this chapter was enlightening on how authorization is handled on the blockchain and the dangers of not having authorization on sensitive functions. In the lab package for the certification and on the final CTF exam, there will be many occurrences of authorization which you can further test your business logic and authorization bypass attacking skills.