// API callback
related_results_labels_thumbs({"version":"1.0","encoding":"UTF-8","feed":{"xmlns":"http://www.w3.org/2005/Atom","xmlns$openSearch":"http://a9.com/-/spec/opensearchrss/1.0/","xmlns$blogger":"http://schemas.google.com/blogger/2008","xmlns$georss":"http://www.georss.org/georss","xmlns$gd":"http://schemas.google.com/g/2005","xmlns$thr":"http://purl.org/syndication/thread/1.0","id":{"$t":"tag:blogger.com,1999:blog-4603622075690903012"},"updated":{"$t":"2023-10-23T04:18:30.735-07:00"},"category":[{"term":"AWS"},{"term":"AWS Certified Solutions Architect"},{"term":"Architect"},{"term":"Certification"},{"term":"AWS Certified Developer"},{"term":"AWS Certified SysOps Administrator"},{"term":"EC2"},{"term":"Course"},{"term":"Development"},{"term":"Cognito"},{"term":"Tutorial"},{"term":"SysOps Administration"},{"term":"CloudFront"},{"term":"News"},{"term":"S3"},{"term":"AutoScaling"},{"term":"Cordova"},{"term":"DynamoDB"},{"term":"ELB"},{"term":"IAM"},{"term":"Node.JS"},{"term":"PhoneGap"},{"term":"Security"},{"term":"Shared Responsibility"},{"term":"VPC"},{"term":"Amazon Aurora"},{"term":"Android"},{"term":"Denial of Service"},{"term":"EBS"},{"term":"IOS"},{"term":"NodeJS"},{"term":"RDS"},{"term":"AWS IoT"},{"term":"AWS Mobile Hub"},{"term":"Amazon Inspector"},{"term":"Amazon QuickSight"},{"term":"AngularJS"},{"term":"CloudTrail"},{"term":"Cloudformation"},{"term":"Dynamic"},{"term":"ECS"},{"term":"India"},{"term":"Internet of Things"},{"term":"Kinesis Firehose"},{"term":"MEAN"},{"term":"MariaDB"},{"term":"PIOPS"},{"term":"PhantomJS"},{"term":"ReactJS"},{"term":"Region"},{"term":"Route 53"},{"term":"Selenium"},{"term":"Trusted Adviser"},{"term":"WAF"},{"term":"Web Application Firewall"},{"term":"YAML"},{"term":"app"},{"term":"example"}],"title":{"type":"text","$t":"BackSpace Academy Blog"},"subtitle":{"type":"html","$t":"A blog about all things Amazon Web Services (AWS) and Cloud Certification."},"link":[{"rel":"http://schemas.google.com/g/2005#feed","type":"application/atom+xml","href":"https:\/\/learn-aws.blogspot.com\/feeds\/posts\/default"},{"rel":"self","type":"application/atom+xml","href":"https:\/\/www.blogger.com\/feeds\/4603622075690903012\/posts\/default\/-\/Denial+of+Service?alt=json-in-script\u0026max-results=6"},{"rel":"alternate","type":"text/html","href":"https:\/\/learn-aws.blogspot.com\/search\/label\/Denial%20of%20Service"},{"rel":"hub","href":"http://pubsubhubbub.appspot.com/"}],"author":[{"name":{"$t":"BackSpace Academy"},"uri":{"$t":"http:\/\/www.blogger.com\/profile\/15061292652079774775"},"email":{"$t":"noreply@blogger.com"},"gd$image":{"rel":"http://schemas.google.com/g/2005#thumbnail","width":"16","height":"16","src":"https:\/\/img1.blogblog.com\/img\/b16-rounded.gif"}}],"generator":{"version":"7.00","uri":"http://www.blogger.com","$t":"Blogger"},"openSearch$totalResults":{"$t":"2"},"openSearch$startIndex":{"$t":"1"},"openSearch$itemsPerPage":{"$t":"6"},"entry":[{"id":{"$t":"tag:blogger.com,1999:blog-4603622075690903012.post-6960110121802518864"},"published":{"$t":"2016-09-12T01:06:00.001-07:00"},"updated":{"$t":"2016-09-26T06:28:48.097-07:00"},"category":[{"scheme":"http://www.blogger.com/atom/ns#","term":"Denial of Service"},{"scheme":"http://www.blogger.com/atom/ns#","term":"EC2"},{"scheme":"http://www.blogger.com/atom/ns#","term":"PhantomJS"},{"scheme":"http://www.blogger.com/atom/ns#","term":"Security"},{"scheme":"http://www.blogger.com/atom/ns#","term":"Selenium"},{"scheme":"http://www.blogger.com/atom/ns#","term":"Shared Responsibility"}],"title":{"type":"text","$t":"Shared Responsibility 3 - Identify and Destroy the Bots"},"content":{"type":"html","$t":"\u003Cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003E\n\u003Ca href=\"https:\/\/manilenya222.files.wordpress.com\/2015\/07\/tg-ff23.jpg\" imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"\u003E\u003Cimg border=\"0\" height=\"264\" src=\"https:\/\/manilenya222.files.wordpress.com\/2015\/07\/tg-ff23.jpg\" width=\"640\" \/\u003E\u003C\/a\u003E\u003C\/div\u003E\n\u003Cdiv style=\"text-align: center;\"\u003E\n\u003Cbr \/\u003E\u003C\/div\u003E\n\u003Cbr \/\u003E\n\u003Cb\u003EPlease note\u003C\/b\u003E: You should have a link in your login for blind or vision impaired people. These techniques will prevent them from using your application. They could be accommodated using an alternative dual factor authentication process.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nIn my last post I detailed how to create dynamic CSS selectors to make life difficult for reliable Bot scripts to be written. The next part of this series is to identify the Bot and take retaliatory action. The \u003Ca href=\"https:\/\/gist.github.com\/BackSpaceTech\/e522d861af3c35d875637bf2bedfed93\" target=\"_blank\"\u003Ecode for this post is available at Gist\u003C\/a\u003E in case Blogger screws it up again.. The code for creating dynamic CSS selectors including some additional decoy elements looks like this:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cpre class=\"brush: js\"\u003Efunction dynamicCSS(){\n var username, password\n x = ''\n if ((Math.random()*2) \u0026gt; 1)\n  x += '\u003Cstyle\u003E.btnSubmit,.password,.username{position:absolute;left:10}.username{top:50px}.password{top:80px}.btnSubmit{top:110px}\u003C\/style\u003E'\n else\n  x += '\u003Cstyle\u003E.btnSubmit,.password,.username{position:absolute;left:10}.username{top:80px}.password{top:110px}.btnSubmit{top:140px}\u003C\/style\u003E'\n x += '\u003Cform\u003E\n'\n x += '\u003Cbr \/\u003E\n\u003Ch1\u003E\nPlease Login\u003C\/h1\u003E\n'\n y = Math.floor((Math.random()*5)) + 2\n for (var a=0; a\u003Cy a=\"\" class=\"username\" id=\"' + username + '\" input=\"\" name=\"' + username + '\" password=\"randonString()\" placeholder=\"Enter Username\" type=\"text\" username=\"randonString()\" x=\"\"\u003E'\n  x += '\u003Cinput class=\"password\" id=\"' + password + '\" name=\"' + password + '\" placeholder=\"Enter Password\" type=\"password\" \/\u003E'\n   x += '\u003Cbutton class=\"btnSubmit\" onclick=\"submitForm(' + '\\'' + username + '\\'' + ',' + '\\'' + password + '\\'' + ')\" type=\"button\"\u003ELog in\u003C\/button\u003E'\n }\n for (var a=0; a\u003Cy a=\"\" class=\"username\" id=\"' + username + '\" input=\"\" name=\"' + username + '\" password=\"randonString()\" placeholder=\"Enter Username\" style=\"visibility: hidden;\" type=\"text\" username=\"randonString()\" x=\"\"\u003E'\n  x += '\u003Cinput class=\"password\" id=\"' + password + '\" name=\"' + password + '\" placeholder=\"Enter Password\" style=\"visibility: hidden;\" type=\"password\" \/\u003E'\n   x += '\u003Cbutton class=\"btnSubmit\" onclick=\"submitForm(' + '\\'' + username + '\\'' + ',' + '\\'' + password + '\\'' + ')\" style=\"visibility: hidden;\" type=\"button\"\u003ELog in\u003C\/button\u003E'\n }\n x += '\u003C\/y\u003E\u003C\/y\u003E\u003C\/form\u003E\n'\n  x += '\u003Cscript src=\"https:\/\/code.jquery.com\/jquery-3.1.0.min.js\"\u003E\u003C\/script\u003E'\n  x += '\u003Cscript src=\"login.js\"\u003E\u003C\/script\u003E'\n return x\n}\n\u003C\/pre\u003E\n\u003Cbr \/\u003E\nIn a real app you would set this all up using a jade template but for simplicity we will just send raw html.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ch3\u003E\nAnalysing Header Information\u003C\/h3\u003E\nThe first thing we can look at is the header information sent to our NodeJS EC2 instance. I conducted some tests using a number of different browsers and also using the very popular PhantomJS headless webkit to find any clear differences between a real browser and a headless browser. Below are the results.\u003Cbr \/\u003E\n\u003Ch4\u003E\nRequest headers from Chrome:\u003C\/h4\u003E\n{\u003Cbr \/\u003E\n\u0026nbsp; \"host\": \"54.197.212.141\",\u003Cbr \/\u003E\n\u0026nbsp; \"connection\": \"keep-alive\",\u003Cbr \/\u003E\n\u0026nbsp; \"content-length\": \"31\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept\": \"*\/*\",\u003Cbr \/\u003E\n\u0026nbsp; \"origin\": \"http:\/\/54.197.212.141\",\u003Cbr \/\u003E\n\u0026nbsp; \"x-requested-with\": \"XMLHttpRequest\",\u003Cbr \/\u003E\n\u0026nbsp; \"user-agent\": \"Mozilla\/5.0 (Windows NT 10.0; WOW64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/52.0.2743.116 Safari\/537.36\",\u003Cbr \/\u003E\n\u0026nbsp; \"content-type\": \"application\/x-www-form-urlencoded; charset=UTF-8\",\u003Cbr \/\u003E\n\u0026nbsp; \"referer\": \"http:\/\/54.197.212.141\/\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-encoding\": \"gzip, deflate\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-language\": \"en-GB,en-US;q=0.8,en;q=0.6\",\u003Cbr \/\u003E\n}\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ch4\u003E\nRequest headers from Firefox:\u003C\/h4\u003E\n{\u003Cbr \/\u003E\n\u0026nbsp; \"host\": \"54.197.212.141\",\u003Cbr \/\u003E\n\u0026nbsp; \"user-agent\": \"Mozilla\/5.0 (Windows NT 10.0; WOW64; rv:48.0) Gecko\/20100101 Firefox\/48.0\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept\": \"*\/*\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-language\": \"en-US,en;q=0.5\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-encoding\": \"gzip, deflate\",\u003Cbr \/\u003E\n\u0026nbsp; \"content-type\": \"application\/x-www-form-urlencoded; charset=UTF-8\",\u003Cbr \/\u003E\n\u0026nbsp; \"x-requested-with\": \"XMLHttpRequest\",\u003Cbr \/\u003E\n\u0026nbsp; \"referer\": \"http:\/\/54.197.212.141\/\",\u003Cbr \/\u003E\n\u0026nbsp; \"content-length\": \"10\",\u003Cbr \/\u003E\n\u0026nbsp; \"connection\": \"keep-alive\"\u003Cbr \/\u003E\n}\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ch4\u003E\nRequest headers from Safari:\u003C\/h4\u003E\n{\u003Cbr \/\u003E\n\u0026nbsp; \"host\": \"54.197.212.141\",\u003Cbr \/\u003E\n\u0026nbsp; \"content-type\": \"application\/x-www-form-urlencoded; charset=UTF-8\",\u003Cbr \/\u003E\n\u0026nbsp; \"origin\": \"http:\/\/54.197.212.141\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-encoding\": \"gzip, deflate\",\u003Cbr \/\u003E\n\u0026nbsp; \"content-length\": \"9\",\u003Cbr \/\u003E\n\u0026nbsp; \"connection\": \"keep-alive\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept\": \"*\/*\",\u003Cbr \/\u003E\n\u0026nbsp; \"user-agent\": \"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit\/601.7.8 (KHTML, like Gecko) Version\/9.1.3 Safari\/601.7.8\",\u003Cbr \/\u003E\n\u0026nbsp; \"referer\": \"http:\/\/54.197.212.141\/\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-language\": \"en-us\",\u003Cbr \/\u003E\n\u0026nbsp; \"x-requested-with\": \"XMLHttpRequest\"\u003Cbr \/\u003E\n}\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ch4\u003E\nRequest headers from Internet Explorer:\u003C\/h4\u003E\n{\u003Cbr \/\u003E\n\u0026nbsp; \"content-type\": \"application\/x-www-form-urlencoded; charset=UTF-8\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept\": \"*\/*\",\u003Cbr \/\u003E\n\u0026nbsp; \"x-requested-with\": \"XMLHttpRequest\",\u003Cbr \/\u003E\n\u0026nbsp; \"referer\": \"http:\/\/54.197.212.141\/\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-language\": \"en-US;q=0.5\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-encoding\": \"gzip, deflate\",\u003Cbr \/\u003E\n\u0026nbsp; \"user-agent\": \"Mozilla\/5.0 (Windows NT 10.0; WOW64; Trident\/7.0; rv:11.0) like Gecko\",\u003Cbr \/\u003E\n\u0026nbsp; \"host\": \"54.197.212.141\",\u003Cbr \/\u003E\n\u0026nbsp; \"content-length\": \"9\",\u003Cbr \/\u003E\n\u0026nbsp; \"dnt\": \"1\",\u003Cbr \/\u003E\n\u0026nbsp; \"connection\": \"Keep-Alive\",\u003Cbr \/\u003E\n\u0026nbsp; \"cache-control\": \"no-cache\"\u003Cbr \/\u003E\n}\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ch4\u003E\nRequest headers from PhantomJS:\u003C\/h4\u003E\n{\u003Cbr \/\u003E\n\u0026nbsp; \"accept\": \"*\/*\",\u003Cbr \/\u003E\n\u0026nbsp; \"referer\": \"http:\/\/54.197.212.141\/\",\u003Cbr \/\u003E\n\u0026nbsp; \"origin\": \"http:\/\/54.197.212.141\",\u003Cbr \/\u003E\n\u0026nbsp; \"x-requested-with\": \"XMLHttpRequest\",\u003Cbr \/\u003E\n\u0026nbsp; \"user-agent\": \"Mozilla\/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko\/20100101 Firefox\/47.0\",\u003Cbr \/\u003E\n\u0026nbsp; \"content-type\": \"application\/x-www-form-urlencoded; charset=UTF-8\",\u003Cbr \/\u003E\n\u0026nbsp; \"content-length\": \"9\",\u003Cbr \/\u003E\n\u0026nbsp; \"connection\": \"Keep-Alive\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-encoding\": \"gzip, deflate\",\u003Cbr \/\u003E\n\u0026nbsp; \"accept-language\": \"en-AU,*\",\u003Cbr \/\u003E\n\u0026nbsp; \"host\": \"54.197.212.141\"\u003Cbr \/\u003E\n}\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nI won't put all the results here for all the browsers tested, but there is a clear structure to the PhantomJS header that is unique from conventional browsers. In particular starting with \"accept\" and finishing with \"host\".\u003Cbr \/\u003E\nAnalysing the header structure can be used to help identify a Bot from a real person.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ch3\u003E\nPlugin Support\u003C\/h3\u003E\nSupport for plugins with headless browsers is minimal or non-existent. We can also look at using the javascript Navigator method on the client to get information on supported plugins and other browser features. A simple test would be to check the plugins array length. In order to do this we need to create a javascript file that runs on the client.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nIn the public directory of your NodeJS create a new file called login.js (use your IP address of course)\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cpre class=\"brush: js\"\u003Efunction submitForm(username, password) {\n  loginURL = 'http:\/\/54.197.212.141' + '\/login'\n  user = $('#' + username).val()\n  pass = $('#' + password).val()\n  $.post( loginURL, { \n    username: 'user',\n    password: 'pass',\n    plugins: '  '\/\/navigator.plugins.length\n  })\n    .done(function( result ) {\n      alert(result)\n      }\n    })\n}\n\u003C\/pre\u003E\n\u003Cbr \/\u003E\nThis code will post back to your NodeJS server information about the browser. Although headless browsers can post forms, from my experiments they don't process jQuery post commands running on the client. In contrast it has worked on all normal browsers without issue\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nIf the post is submitted then the further identification can occur. In this example we will just check the length of the plugins array and also send the filled out input fields. If it is a Bot then the wrong input fields will be submitted and the plugins array length will be zero.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nYou will also need to install and enable bodyParser in your index.js file to receive the information:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nvar bodyParser = require('body-parser')\u003Cbr \/\u003E\napp.use(bodyParser.urlencoded({ extended: true }));\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nYou will also need to update the dynamicCSS function to include jQuery on the client:\u003Cbr \/\u003E\n\u003Cspan style=\"background-color: white; color: #333333; font-family: \u0026quot;consolas\u0026quot; , \u0026quot;liberation mono\u0026quot; , \u0026quot;menlo\u0026quot; , \u0026quot;courier\u0026quot; , monospace; font-size: 12px; line-height: 20px; white-space: pre;\"\u003E\u003Cbr \/\u003E\u003C\/span\u003E\n\u003Cspan style=\"background-color: white; color: #333333; font-family: \u0026quot;consolas\u0026quot; , \u0026quot;liberation mono\u0026quot; , \u0026quot;menlo\u0026quot; , \u0026quot;courier\u0026quot; , monospace; font-size: 12px; line-height: 20px; white-space: pre;\"\u003E  \u003C\/span\u003E\u003Cspan style=\"color: #333333; font-family: \u0026quot;consolas\u0026quot; , \u0026quot;liberation mono\u0026quot; , \u0026quot;menlo\u0026quot; , \u0026quot;courier\u0026quot; , monospace;\"\u003E\u003Cspan style=\"font-size: 12px; line-height: 20px; white-space: pre;\"\u003Ex += '\u0026lt;script src=\"https:\/\/code.jquery.com\/jquery-3.1.0.min.js\"\u0026gt;\u0026lt;\/script\u0026gt;'\u003C\/span\u003E\u003C\/span\u003E\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nAlso add a link to to your login.js file:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cspan style=\"color: #333333; font-family: \u0026quot;consolas\u0026quot; , \u0026quot;liberation mono\u0026quot; , \u0026quot;menlo\u0026quot; , \u0026quot;courier\u0026quot; , monospace; font-size: 12px; line-height: 20px; white-space: pre;\"\u003Ex += '\u0026lt;script src=\"login.js\"\u0026gt;\u0026lt;\/script\u0026gt;'\u003C\/span\u003E\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nAdditional security can also be achieved by creating multiple versions of the login file with different names for the parameters (username\/password\/plugins). The backend code that handles the post request would be expecting the fields defined in login file that was served. Anything different that is posted means the request is most probably a from a Bot. Another option is to serve the login.js file on the server side with random parameters and set up a route for it to be downloaded. For simplicity I won't add the extra code for this, but implementation is quite straightforward.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ch3\u003E\nBuilding a Profile of the Bot\u003C\/h3\u003E\n\u003Cbr \/\u003E\nIt is quite important that you use a number of techniques to identify bots to make sure you do not have a case of mistaken identity. It is a good idea to use a score system and identify a level which will trigger corrective action.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nIn your index.js file that is run on your server update with the following code:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cpre class=\"brush: js\"\u003Eapp.post('\/login', function(request, response) {\n  var botScore = 0\n  if (request.body.plugins == 0) ++botScore \/\/ Empty plugins array\n  if (!request.body.username) ++botScore \/\/ Clicked on decoy inputs\n  if (!request.body.password) ++botScore\n  if (getObjectKeyIndex(request.headers, 'host') != 0) ++botScore \/\/ Bot type header info\n  if (getObjectKeyIndex(request.headers, 'accept') == 0) ++botScore\n  if (getObjectKeyIndex(request.headers, 'referer') == 1) ++botScore\n  if (getObjectKeyIndex(request.headers, 'origin') == 2) ++botScore\n  console.log('Bot score = ' + botScore)\n  if (botScore \u0026gt; 4) {\n    console.log('Destroy Bot')\n    response.send('fail')\n  }\n  else {\n    response.send('Logged in ' + request.body.username)\n  }\n})\n\u003C\/pre\u003E\n\u003Cbr \/\u003E\nWe are now building a bot score and deciding whether to allow access based on that score.\u003Cbr \/\u003E\n\u003Ch3\u003E\nAttacking the Bot with Javascript\u003C\/h3\u003E\nThe following technique needs to be used with caution.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nIf you want to ensure the Bot is destroyed for the good of the internet community then you can look at launching a counterattack on the bot with your own malicious script to crash the browser.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nThis can be achieved quite simply using an endless loop that reloads the page. This will stop execution of the browser and eventually cause it to crash. Update your client side code in login.js with:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cpre class=\"brush: js\"\u003Efunction submitForm(username, password) {\n  loginURL = 'http:\/\/54.197.212.141' + '\/login'\n  user = $('#' + username).val()\n  pass = $('#' + password).val()\n  $.post( loginURL, { \n    username: 'user',\n    password: 'pass',\n    plugins:   navigator.plugins.length\n  })\n    .done(function( result ) {\n      if (result == 'fail')\n        while(true) location.reload(true) \/\/ Crash the Bot\n      else {\n        alert(result)\n      }\n    })\n}\n\u003C\/pre\u003E\n\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nIn my next post I will look at the different types and benefits of captcha and how to enable them on your site.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nBe sure to subscribe to the blog so that you can get the latest updates.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nFor more AWS training and tutorials check out\u0026nbsp;\u003Ca href=\"https:\/\/backspace.academy\/\"\u003Ebackspace.academy\u003C\/a\u003E\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ca href=\"https:\/\/backspace.academy\/\" target=\"_blank\"\u003E\u003Cimg src=\"https:\/\/backspace.academy\/assets\/img\/logo.svg\" \/\u003E\u003C\/a\u003E"},"link":[{"rel":"replies","type":"application/atom+xml","href":"https:\/\/learn-aws.blogspot.com\/feeds\/6960110121802518864\/comments\/default","title":"Post Comments"},{"rel":"replies","type":"text/html","href":"https:\/\/learn-aws.blogspot.com\/2016\/09\/shared-responsibility-3-identify-and.html#comment-form","title":"0 Comments"},{"rel":"edit","type":"application/atom+xml","href":"https:\/\/www.blogger.com\/feeds\/4603622075690903012\/posts\/default\/6960110121802518864"},{"rel":"self","type":"application/atom+xml","href":"https:\/\/www.blogger.com\/feeds\/4603622075690903012\/posts\/default\/6960110121802518864"},{"rel":"alternate","type":"text/html","href":"https:\/\/learn-aws.blogspot.com\/2016\/09\/shared-responsibility-3-identify-and.html","title":"Shared Responsibility 3 - Identify and Destroy the Bots"}],"author":[{"name":{"$t":"BackSpace Academy"},"uri":{"$t":"http:\/\/www.blogger.com\/profile\/15061292652079774775"},"email":{"$t":"noreply@blogger.com"},"gd$image":{"rel":"http://schemas.google.com/g/2005#thumbnail","width":"16","height":"16","src":"https:\/\/img1.blogblog.com\/img\/b16-rounded.gif"}}],"thr$total":{"$t":"0"}},{"id":{"$t":"tag:blogger.com,1999:blog-4603622075690903012.post-6695927890245298364"},"published":{"$t":"2016-09-04T20:51:00.004-07:00"},"updated":{"$t":"2016-09-26T06:29:18.130-07:00"},"category":[{"scheme":"http://www.blogger.com/atom/ns#","term":"AWS"},{"scheme":"http://www.blogger.com/atom/ns#","term":"Denial of Service"},{"scheme":"http://www.blogger.com/atom/ns#","term":"IAM"},{"scheme":"http://www.blogger.com/atom/ns#","term":"NodeJS"},{"scheme":"http://www.blogger.com/atom/ns#","term":"Security"},{"scheme":"http://www.blogger.com/atom/ns#","term":"Shared Responsibility"}],"title":{"type":"text","$t":"Shared Responsibility 2 - Using Dynamic CSS Selectors to stop the bots."},"content":{"type":"html","$t":"\u003Cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003E\n\u003Ca href=\"https:\/\/media.licdn.com\/mpr\/mpr\/AAEAAQAAAAAAAATwAAAAJGU3YjNlNGFmLTc1NDQtNDJiMi1iYWYwLTRiOTA2ZTNjNjY0MQ.jpg\" imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"\u003E\u003Cimg border=\"0\" height=\"364\" src=\"https:\/\/media.licdn.com\/mpr\/mpr\/AAEAAQAAAAAAAATwAAAAJGU3YjNlNGFmLTc1NDQtNDJiMi1iYWYwLTRiOTA2ZTNjNjY0MQ.jpg\" width=\"640\" \/\u003E\u003C\/a\u003E\u003C\/div\u003E\n\u003Cbr \/\u003E\nIn my \u003Ca href=\"http:\/\/blog.backspace.academy\/2016\/08\/shared-responsibility-stopping-threats.html\"\u003Elast post\u003C\/a\u003E I talked about techniques to stop malicious web automation services at the source before they reach AWS infrastructure. Now we will get our hands dirty with some code to put it into action. Don't worry if you are not an experienced coder, you should still be able to follow along.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ch3\u003E\nHow do Bot scripts work?\u003C\/h3\u003E\nA rendered web page contains a Document Object Model (DOM). The DOM defines all the elements on the page such as forms and input fields. Bots mimic a real user that enters information in fields, clicks on buttons etc. To do this the bot needs to identify the relevant elements in the DOM. DOM elements are identified using CSS selectors. Bot scripts consist of a series of steps that detail CSS selectors and what action to perform on them.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nThe DOM structure and elements of a page can be quickly identified using a browser. Pressing F12 in your browser will launch developer tools with this information:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003E\n\u003Ca href=\"https:\/\/developer.chrome.com\/devtools\/images\/devtools-window.png\" imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"\u003E\u003Cimg border=\"0\" height=\"230\" src=\"https:\/\/developer.chrome.com\/devtools\/images\/devtools-window.png\" width=\"640\" \/\u003E\u003C\/a\u003E\u003C\/div\u003E\n\u003Cbr \/\u003E\nTo see specific details of a DOM element simply right click on the element on the page and select 'inspect':\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003E\n\u003Ca href=\"https:\/\/1.bp.blogspot.com\/-ZBXiW_-KUqE\/V8yof19_FhI\/AAAAAAAAAV0\/xn6pkztN924SlSN7lEQPVJPrSLsPOya-gCLcB\/s1600\/dom-inspect.png\" imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"\u003E\u003Cimg border=\"0\" src=\"https:\/\/1.bp.blogspot.com\/-ZBXiW_-KUqE\/V8yof19_FhI\/AAAAAAAAAV0\/xn6pkztN924SlSN7lEQPVJPrSLsPOya-gCLcB\/s1600\/dom-inspect.png\" \/\u003E\u003C\/a\u003E\u003C\/div\u003E\n\u003Cbr \/\u003E\nThis will open up the developer tools with the element identified. You can get the CSS selector for the element easily by again right clicking on the element in the developer tools:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003E\n\u003Ca href=\"https:\/\/2.bp.blogspot.com\/-p9sbSmdhswM\/V8ypdqXodEI\/AAAAAAAAAV4\/mUrbDihE3nMDUAzCJ_EXNJI60fVM6Kt-wCLcB\/s1600\/dom-selector.png\" imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"\u003E\u003Cimg border=\"0\" src=\"https:\/\/2.bp.blogspot.com\/-p9sbSmdhswM\/V8ypdqXodEI\/AAAAAAAAAV4\/mUrbDihE3nMDUAzCJ_EXNJI60fVM6Kt-wCLcB\/s1600\/dom-selector.png\" \/\u003E\u003C\/a\u003E\u003C\/div\u003E\n\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nNote that this will be only one representation of the element as a CSS selector (generally the shortest one). There are a number of ways an element can be defined as a CSS selector including:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cul\u003E\n\u003Cli\u003Eid name\u003C\/li\u003E\n\u003Cli\u003Einput name\u003C\/li\u003E\n\u003Cli\u003Eclass names\u003C\/li\u003E\n\u003Cli\u003EDOM traversal e.g. defining its chain of parent elements in the DOM\u003C\/li\u003E\n\u003Cli\u003EText inside the element using Jquery ':contains'.\u003C\/li\u003E\n\u003C\/ul\u003E\n\u003Cbr \/\u003E\n\u003Ch3\u003E\nDynamic CSS Selectors\u003C\/h3\u003E\nTo make life difficult to develop bot scripts you can use dynamic CSS selectors. Instead of creating the same CSS selectors each time your page is rendered, you can look at changing these randomly each time.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nWhen using NodeJS and Express this is quite straightforward as your are already rendering pages on the server. Simply introduce some code to mix this up a bit.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ch3\u003E\nLet's Start\u003C\/h3\u003E\n\u003Cbr \/\u003E\nFirst of all set up an EC2 instance with NodeJS and Express set up to render pages. If you are unsure you can view the video below:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Ca href=\"https:\/\/vimeo.com\/145017165\" target=\"_blank\"\u003Ehttps:\/\/vimeo.com\/145017165\u003C\/a\u003E\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nTo save you typing, the \u003Ca href=\"https:\/\/gist.github.com\/BackSpaceTech\/41ed0973b96cf1360dc788ad5219a7b9\" target=\"_blank\"\u003Ecode is available at Gist\u003C\/a\u003E\u0026nbsp;(also Blogger tends to screw up code when it is published).\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nNow let's change index.js to create a simple login form.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nPoint your browser to the public IP address of your instance to check everything is ok. e.g. xxx.xxx.xxx.xxx:8080\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nNow change the index.js file to include a dynamicCSS function :\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cpre class=\"brush: js\"\u003Eapp.get('\/', function(request, response) {\n  response.send(dynamicCSS())\n})\n\nfunction dynamicCSS(){\n x = ''\n x += '\u003Cform\u003E\n';\n x += '\u003Cbr \/\u003E\n\u003Ch1\u003E\nPlease Login\u003C\/h1\u003E\n;\n x += '\u003Cinput id=\"username\" name=\"username\" placeholder=\"Enter Username\" type=\"text\" \/\u003E';\n x += '\u003Cinput id=\"password\" name=\"password\" placeholder=\"Enter Password\" type=\"password\" \/\u003E'; \n x += '\u003Cbutton type=\"submit\"\u003ELog in\u003C\/button\u003E'\n x += '\u003C\/form\u003E\n';\n return x\n}\n\u003C\/pre\u003E\n\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nNow do npm start at the command line of your ec2 instance and refresh the browser page. You will now see our very simple login form:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003E\n\u003Ca href=\"https:\/\/1.bp.blogspot.com\/-p9LLTCAWglQ\/V8zMfrp6eDI\/AAAAAAAAAWM\/gahpx2v92NIfU6tRi2aH8owa7SbkCO8mQCLcB\/s1600\/login.PNG\" imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"\u003E\u003Cimg border=\"0\" height=\"74\" src=\"https:\/\/1.bp.blogspot.com\/-p9LLTCAWglQ\/V8zMfrp6eDI\/AAAAAAAAAWM\/gahpx2v92NIfU6tRi2aH8owa7SbkCO8mQCLcB\/s320\/login.PNG\" width=\"320\" \/\u003E\u003C\/a\u003E\u003C\/div\u003E\n\u003Cbr \/\u003E\nThe problem with this form is that it is really easy to identify the dom elements required to login. The id, name, placeholder all refer to username or password.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nNow let's change our code and introduce dynamically created CSS selectors.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cpre class=\"brush: js\"\u003Evar loginElements = {\n username: '',\n password: ''\n }\n\nfunction dynamicCSS(){\n var username = randomString()\n var password = randomString()\n loginElements.username = username\n loginElements.password = password \n x = ''\n x += '\u003Cform\u003E\n'\n x += '\u003Cbr \/\u003E\n\u003Ch1\u003E\nPlease Login\u003C\/h1\u003E\n'\n x += '\u003Cinput id=\"' + username + '\" name=\"' + username + '\" placeholder=\"Enter Username\" type=\"text\" \/\u003E'\n x += '\u003Cinput id=\"' + password + '\" name=\"' + password + '\" placeholder=\"Enter Password\" type=\"password\" \/\u003E'\n x += '\u003Cbutton type=\"submit\"\u003ELog in\u003C\/button\u003E'\n x += '\u003C\/form\u003E\n'\n return x\n}\n\nfunction randomString(){\n chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'.split('')\n chars.sort(function() {\n   return 0.5 - Math.random()\n })\n return chars.splice(0, 8).toString().replace(\/,\/g, '')\n}\n\u003C\/pre\u003E\n\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nThis now generates a random string for the id and name tags of the input elements. This makes it not possible to use these in a reliable bot script. If you do npm start again and view the the view the element in developer tools you can see the random strings.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nWe now need to look at the other ways our elements can be identified as CSS selectors. As you can see the text \"username\" and \"password\" is still used in the placeholders and input type tag. Also the DOM structure itself doesn't change dynamically, making it possible to reference the element through traversing the DOM structure.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nWe will address both problems by creating random decoy input elements with the same parameters. The CSS position property will allow us to stack them on top of each other so that the decoy elements are not visible on the page:\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cpre class=\"brush: js\"\u003Eapp.get('\/', function(request, response) {\n  response.send(dynamicCSS())\n})\n\nvar loginElements = {\n  username: '',\n  password: ''\n}\n\nfunction dynamicCSS(){\n  var username, password\n  x = ''\n  x += '\u003Cform\u003E\n'\n  x += '\u003Cbr \/\u003E\n\u003Ch1\u003E\nPlease Login\u003C\/h1\u003E\n'\n  y = Math.floor((Math.random()*5)) + 2\n  for (var a=0; a\u003Cy a=\"\" class=\"username\" id=\"' + username + '\" input=\"\" name=\"' + username + '\" password=\"randonString()\" placeholder=\"Enter Username\" type=\"text\" username=\"randonString()\" x=\"\"\u003E'\n    x += '\u003Cinput class=\"password\" id=\"' + password + '\" name=\"' + password + '\" placeholder=\"Enter Password\" type=\"password\" \/\u003E'\n    loginElements.username = username\n    loginElements.password = password\n  }\n  x += '\u003Cbutton class=\"btnSubmit\" type=\"submit\"\u003ELog in\u003C\/button\u003E'\n  x += '\u003C\/y\u003E\u003C\/form\u003E\n'\n  return x\n}\n\nfunction randonString(){\n  chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'.split('')\n  chars.sort(function() {\n  return 0.5 - Math.random()\n  })\n  return chars.splice(0, 8).toString().replace(\/,\/g, '')\n}\n\n\u003C\/pre\u003E\n\u003Cbr \/\u003E\n\u003Cdiv\u003E\n\u003Cbr \/\u003E\u003C\/div\u003E\nNow when you view the DOM in your browser developer tools, \u0026nbsp;you can see the decoy input elements created underneath the real input element. If you refresh your browser you will see a different number of elements created each time (between 1 and 5 created).\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cdiv class=\"separator\" style=\"clear: both; text-align: center;\"\u003E\n\u003Ca href=\"https:\/\/3.bp.blogspot.com\/-znOd_64MkUE\/V8zn0_MJ-AI\/AAAAAAAAAWc\/AWrsqzkNfp4eWSuw80Uha-3sJjFp_-QQgCLcB\/s1600\/dom-dynamic.png\" imageanchor=\"1\" style=\"margin-left: 1em; margin-right: 1em;\"\u003E\u003Cimg border=\"0\" src=\"https:\/\/3.bp.blogspot.com\/-znOd_64MkUE\/V8zn0_MJ-AI\/AAAAAAAAAWc\/AWrsqzkNfp4eWSuw80Uha-3sJjFp_-QQgCLcB\/s1600\/dom-dynamic.png\" \/\u003E\u003C\/a\u003E\u003C\/div\u003E\n\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nThe bot creator can no longer use the username and password placeholders or input types to identify the elements. They can also not use the DOM structure to traverse through the DOM as this is changing also. As pointed out by a reader of this post (thanks Vadim!), you should also put some random inputs after to handle jquery \":last\". A good place would be underneath your logo.\u003Cbr \/\u003E\n\u003Cpre class=\"brush: js\"\u003Ey = Math.floor((Math.random()*5)) + 2\nfor (var a=0; a\u003Cy a=\"\" class=\"username\" id=\"' + username + '\" input=\"\" name=\"' + username + '\" password=\"randonString()\" placeholder=\"Enter Username\" type=\"text\" username=\"randonString()\" x=\"\"\u003E'\n  x += '\u003Cinput class=\"password\" id=\"' + password + '\" name=\"' + password + '\" placeholder=\"Enter Password\" type=\"password\" \/\u003E'\n  loginElements.username = username\n  loginElements.password = password\n}\nfor (var a=0; a\u003Cy a=\"\" class=\"username\" id=\"' + username + '\" input=\"\" name=\"' + username + '\" password=\"randonString()\" placeholder=\"Enter Username\" type=\"text\" username=\"randonString()\" x=\"\"\u003E'\n  x += '\u003Cinput class=\"password\" id=\"' + password + '\" name=\"' + password + '\" placeholder=\"Enter Password\" type=\"password\" \/\u003E'\n  document.getElementById(username).style.visibility = \"hidden\";\n  document.getElementById(password).style.visibility = \"hidden\";\n}\n\u003C\/y\u003E\u003C\/y\u003E\u003C\/pre\u003E\n\u003Cbr \/\u003E\nThe next thing a bot script can do is click on an x-y position on the screen. We can handle this by randomly changing the position of the elements.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\n\u003Cpre class=\"brush: js\"\u003Evar loginElements = {\n username: '',\n password: ''\n }\n\nfunction dynamicCSS(){\n  var username, password\n  x = ''\n  if ((Math.random()*2) \u0026gt; 1)\n    x += '\u003Cstyle\u003E.btnSubmit,.password,.username{position:absolute;left:10}.username{top:50px}.password{top:80px}.btnSubmit{top:110px}\u003C\/style\u003E'\n  else\n    x += '\u003Cstyle\u003E.btnSubmit,.password,.username{position:absolute;left:10}.username{top:80px}.password{top:110px}.btnSubmit{top:140px}\u003C\/style\u003E'\n  x += '\u003Cform\u003E\n'\n  x += '\u003Cbr \/\u003E\n\u003Ch1\u003E\nPlease Login\u003C\/h1\u003E\n'\n  y = Math.floor((Math.random()*5)) + 2\n  for (var a=0; a\u003Cy a=\"\" class=\"username\" id=\"' + username + '\" input=\"\" name=\"' + username + '\" password=\"randonString()\" placeholder=\"Enter Username\" type=\"text\" username=\"randonString()\" x=\"\"\u003E'\n    x += '\u003Cinput class=\"password\" id=\"' + password + '\" name=\"' + password + '\" placeholder=\"Enter Password\" type=\"password\" \/\u003E'\n    loginElements.username = username\n    loginElements.password = password\n  }\n  x += '\u003Cbutton class=\"btnSubmit\" type=\"submit\"\u003ELog in\u003C\/button\u003E'\n  x += '\u003C\/y\u003E\u003C\/form\u003E\n'\n  return x\n}\nfunction randomString(){\n chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'.split('')\n chars.sort(function() {\n   return 0.5 - Math.random()\n })\n return chars.splice(0, 8).toString().replace(\/,\/g, '')\n}\n\u003C\/pre\u003E\n\u003Cbr \/\u003E\n\u003Cdiv\u003E\n\u003Cbr \/\u003E\u003C\/div\u003E\n\u003Cdiv\u003E\nThe position of the input elements is now random. This currently only has two positions but you can elaborate on this to create many possible combinations of positions. You may also make your login form inside a modal window that changes position on the screen.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nIf want to go further you can look having two login forms, username followed by password. Or even better, randomly change between the two.\u003C\/div\u003E\n\u003Cdiv\u003E\n\u003Cbr \/\u003E\u003C\/div\u003E\n\u003Cdiv\u003E\nWe have now addressed the possible techniques a bot creator can use to identify your input elements and login to your site.\u003C\/div\u003E\n\u003Cdiv\u003E\n\u003Cbr \/\u003E\u003C\/div\u003E\n\u003Cdiv\u003E\nCongratulations, you made it to the end!\u003C\/div\u003E\n\u003Ch3\u003E\nWhat's next?\u003C\/h3\u003E\nIn \u003Ca href=\"http:\/\/blog.backspace.academy\/2016\/09\/shared-responsibility-3-identify-and.html\" target=\"_blank\"\u003Emy next post\u003C\/a\u003E I will introduce techniques to identify bots and then look at launching a counter attack on the bot to crash it after it has been positively identified.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nBe sure to subscribe to the blog so that you can get the latest updates.\u003Cbr \/\u003E\n\u003Cbr \/\u003E\nFor more AWS training and tutorials check out\u0026nbsp;\u003Ca href=\"https:\/\/backspace.academy\/\"\u003Ebackspace.academy\u003C\/a\u003E\n\u003Ca href=\"https:\/\/backspace.academy\/\" target=\"_blank\"\u003E\u003Cimg src=\"https:\/\/backspace.academy\/assets\/img\/logo.svg\" \/\u003E\u003C\/a\u003E"},"link":[{"rel":"replies","type":"application/atom+xml","href":"https:\/\/learn-aws.blogspot.com\/feeds\/6695927890245298364\/comments\/default","title":"Post Comments"},{"rel":"replies","type":"text/html","href":"https:\/\/learn-aws.blogspot.com\/2016\/09\/shared-responsibility-2-using-dynamic.html#comment-form","title":"2 Comments"},{"rel":"edit","type":"application/atom+xml","href":"https:\/\/www.blogger.com\/feeds\/4603622075690903012\/posts\/default\/6695927890245298364"},{"rel":"self","type":"application/atom+xml","href":"https:\/\/www.blogger.com\/feeds\/4603622075690903012\/posts\/default\/6695927890245298364"},{"rel":"alternate","type":"text/html","href":"https:\/\/learn-aws.blogspot.com\/2016\/09\/shared-responsibility-2-using-dynamic.html","title":"Shared Responsibility 2 - Using Dynamic CSS Selectors to stop the bots."}],"author":[{"name":{"$t":"BackSpace Academy"},"uri":{"$t":"http:\/\/www.blogger.com\/profile\/15061292652079774775"},"email":{"$t":"noreply@blogger.com"},"gd$image":{"rel":"http://schemas.google.com/g/2005#thumbnail","width":"16","height":"16","src":"https:\/\/img1.blogblog.com\/img\/b16-rounded.gif"}}],"media$thumbnail":{"xmlns$media":"http://search.yahoo.com/mrss/","url":"https:\/\/1.bp.blogspot.com\/-ZBXiW_-KUqE\/V8yof19_FhI\/AAAAAAAAAV0\/xn6pkztN924SlSN7lEQPVJPrSLsPOya-gCLcB\/s72-c\/dom-inspect.png","height":"72","width":"72"},"thr$total":{"$t":"2"}}]}});