<p><div><div><div><div data-show-faces="false" data-height="21" data-width="100" data-layout="button_count" data-send="true" data-href="https://www.r-bloggers.com/handling-semantic-version-strings-like-a-boss-with-the-semver-package/"></div></div><div></div></div><div></div></div>
<div>
(This article was first published on <strong><a href="https://rud.is/b/2018/03/07/handling-semantic-version-string-like-a-boss-with-the-semver-package/"> R – rud.is</a></strong>, and kindly contributed to <a rel="nofollow" href="https://www.r-bloggers.com/">R-bloggers)</a> </div>
<p>I work with internet-scale data and do my fair share of macro-analyses on vulnerabilities. I use the R <a rel="nofollow" href="https://cran.r-project.org/package=semver" target="_blank"><code>semver</code></a> package for most of my work and wanted to blather on a bit about it since it’s super-helpful for this work and doesn’t get the attention it deserves. <code>semver</code> makes it possible to create charts like this:</p>
<p><a rel="nofollow" href="https://i0.wp.com/rud.is/b/wp-content/uploads/2018/03/unnamed-chunk-6-1.png?ssl=1" target="_blank"><img data-orig-file="https://i0.wp.com/rud.is/b/wp-content/uploads/2018/03/unnamed-chunk-6-1.png?fit=1920%2C768&amp;ssl=1" data-orig-size="1920,768" data-medium-file="https://i0.wp.com/rud.is/b/wp-content/uploads/2018/03/unnamed-chunk-6-1.png?fit=300%2C120&amp;ssl=1" data-attachment-id="8516" data-image-meta="{" data-image-description="" src="https://i0.wp.com/rud.is/b/wp-content/uploads/2018/03/unnamed-chunk-6-1.png?resize=780%2C312&ssl=1" data-permalink="https://rud.is/b/2018/03/07/handling-semantic-version-string-like-a-boss-with-the-semver-package/unnamed-chunk-6-1/" data-large-file="https://i0.wp.com/rud.is/b/wp-content/uploads/2018/03/unnamed-chunk-6-1.png?fit=530%2C212&amp;ssl=1" alt="" data-image-title="unnamed-chunk-6-1" width="450" data-comments-opened="1" class="wp-image-8516" data-recalc-dims="1"></a></p>
<p>which are very helpful in when conducting exposure analytics.</p>
<p>We’ll need a few packages to help us along the way:</p>
<pre><code>library(here) # file mgmt
library(semver) # the whole purpose of the blog post
library(rvest) # we'll need this to get version->year mappings
library(stringi) # b/c I'm still too lazy to switch to ore
library(hrbrthemes) # pretty graphs
library(tidyverse) # sane data processing idioms</code></pre>
<p>By issuing a <code>stats</code> command to a <code>memcached</code> instance you can get a full list of statistics for the server. The recent newsmaking DDoS used this feature in conjunction with address spoofing to create 30 minutes of chaos for GitHub.</p>
<p>I sent a <code>stats</code> command (followed by a newline) to a vanilla <code>memcached</code> installation and it returned 53 lines (1108 bytes) of <code>STAT</code> results that look something like this:</p>
<pre><code>STAT pid 7646
STAT uptime 141
STAT time 1520447469
STAT version 1.4.25 Ubuntu
STAT libevent 2.0.21-stable
...
</code></pre>
<p>The <code>version</code> bit is what we’re after, but there are plenty of other variables you could just as easily focus on if you use <code>memcached</code> in any production capacity.</p>
<p>I extracted raw version response data from our most recent scan for open <code>memcached</code> servers on the internet. For ethical reasons, I cannot blindly share the entire raw data set but hit up research@rapid7.com if you have a need or desire to work with this data.</p>
<p>Let’s read it in and take a look:</p>
<pre><code>version_strings &lt;- read_lines(here(&quot;data&quot;, &quot;versions.txt&quot;))

set.seed(2018-03-07)

sample(version_strings, 50)

##  [1] &quot;STAT version 1.4.5&quot;             &quot;STAT version 1.4.17&quot;           
##  [3] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.4.31&quot;           
##  [5] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.2.6&quot;            
##  [7] &quot;STAT version 1.2.6&quot;             &quot;STAT version 1.4.15&quot;           
##  [9] &quot;STAT version 1.4.17&quot;            &quot;STAT version 1.4.4&quot;            
## [11] &quot;STAT version 1.4.5&quot;             &quot;STAT version 1.2.6&quot;            
## [13] &quot;STAT version 1.4.2&quot;             &quot;STAT version 1.4.14 (Ubuntu)&quot;  
## [15] &quot;STAT version 1.4.7&quot;             &quot;STAT version 1.4.39&quot;           
## [17] &quot;STAT version 1.4.4-14-g9c660c0&quot; &quot;STAT version 1.2.6&quot;            
## [19] &quot;STAT version 1.2.6&quot;             &quot;STAT version 1.4.14&quot;           
## [21] &quot;STAT version 1.4.4-14-g9c660c0&quot; &quot;STAT version 1.4.37&quot;           
## [23] &quot;STAT version 1.4.13&quot;            &quot;STAT version 1.4.4&quot;            
## [25] &quot;STAT version 1.4.17&quot;            &quot;STAT version 1.2.6&quot;            
## [27] &quot;STAT version 1.4.37&quot;            &quot;STAT version 1.4.13&quot;           
## [29] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.4.15&quot;           
## [31] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.2.6&quot;            
## [33] &quot;STAT version 1.4.10&quot;            &quot;STAT version 1.4.25&quot;           
## [35] &quot;STAT version 1.4.25&quot;            &quot;STAT version 1.4.9&quot;            
## [37] &quot;STAT version 1.4.30&quot;            &quot;STAT version 1.4.21&quot;           
## [39] &quot;STAT version 1.4.15&quot;            &quot;STAT version 1.4.31&quot;           
## [41] &quot;STAT version 1.4.13&quot;            &quot;STAT version 1.2.6&quot;            
## [43] &quot;STAT version 1.4.13&quot;            &quot;STAT version 1.4.15&quot;           
## [45] &quot;STAT version 1.4.19&quot;            &quot;STAT version 1.4.25 Ubuntu&quot;    
## [47] &quot;STAT version 1.4.37&quot;            &quot;STAT version 1.4.4-14-g9c660c0&quot;
## [49] &quot;STAT version 1.2.6&quot;             &quot;STAT version 1.4.25 Ubuntu&quot;</code></pre>
<p>It’s in decent shape, but it needs some work if we’re going to do a version analysis with it. Let’s clean it up a bit:</p>
<pre><code>data_frame(
  string = stri_match_first_regex(version_strings, &quot;STAT version (.*)$&quot;)[,2]
) -> versions

count(versions, string, sort = TRUE) %>%
  knitr::kable(format=&quot;markdown&quot;)</code></pre>
<table>
<thead>
<tr>
<th align="left">string</th>
<th align="right">n</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">1.4.15</td>
<td align="right">1966</td>
</tr>
<tr>
<td align="left">1.2.6</td>
<td align="right">1764</td>
</tr>
<tr>
<td align="left">1.4.17</td>
<td align="right">1101</td>
</tr>
<tr>
<td align="left">1.4.37</td>
<td align="right">949</td>
</tr>
<tr>
<td align="left">1.4.13</td>
<td align="right">725</td>
</tr>
<tr>
<td align="left">1.4.4</td>
<td align="right">531</td>
</tr>
<tr>
<td align="left">1.4.25</td>
<td align="right">511</td>
</tr>
<tr>
<td align="left">1.4.20</td>
<td align="right">368</td>
</tr>
<tr>
<td align="left">1.4.14 (Ubuntu)</td>
<td align="right">334</td>
</tr>
<tr>
<td align="left">1.4.21</td>
<td align="right">309</td>
</tr>
<tr>
<td align="left">1.4.25 Ubuntu</td>
<td align="right">290</td>
</tr>
<tr>
<td align="left">1.4.24</td>
<td align="right">259</td>
</tr>
<tr>
<td align="left">…</td>
<td align="right">…</td>
</tr>
</tbody>
</table>
<p>Much better! However, we really only need the major parts of the <a rel="nofollow" href="https://semver.org/" target="_blank">semantic version string</a> for a macro view, so let’s remove non-version strings completely and extract just the <em>major</em>, <em>minor</em> and <em>patch</em> bits:</p>
<pre><code>filter(versions, !stri_detect_fixed(string, &quot;UNKNOWN&quot;)) %>% # get rid of things we can't use
  mutate(string = stri_match_first_regex(
    string, &quot;([[:digit:]]+\\.[[:digit:]]+\\.[[:digit:]]+)&quot;)[,2] # for a macro-view, the discrete sub-versions aren't important
  ) -> versions

count(versions, string, sort = TRUE) %>%
  knitr::kable(format=&quot;markdown&quot;)</code></pre>
<table>
<thead>
<tr>
<th align="left">string</th>
<th align="right">n</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">1.4.15</td>
<td align="right">1966</td>
</tr>
<tr>
<td align="left">1.2.6</td>
<td align="right">1764</td>
</tr>
<tr>
<td align="left">1.4.17</td>
<td align="right">1101</td>
</tr>
<tr>
<td align="left">1.4.37</td>
<td align="right">949</td>
</tr>
<tr>
<td align="left">1.4.25</td>
<td align="right">801</td>
</tr>
<tr>
<td align="left">1.4.4</td>
<td align="right">747</td>
</tr>
<tr>
<td align="left">1.4.13</td>
<td align="right">727</td>
</tr>
<tr>
<td align="left">1.4.14</td>
<td align="right">385</td>
</tr>
<tr>
<td align="left">1.4.20</td>
<td align="right">368</td>
</tr>
<tr>
<td align="left">1.4.21</td>
<td align="right">309</td>
</tr>
<tr>
<td align="left">1.4.24</td>
<td align="right">264</td>
</tr>
<tr>
<td align="left">…</td>
<td align="right">…</td>
</tr>
</tbody>
</table>
<p>Much, <em>much</em> better! Now, let’s dig into the versions a bit. Using <code>semver</code> is dirt-simple. Just use <code>parse_version()</code> to get the usable bits out:</p>
<pre><code>ex_ver &lt;- semver::parse_version(head(versions$string[1]))

ex_ver
## [1] Maj: 1 Min: 4 Pat: 25

str(ex_ver)
## List of 1
##  $ :Class 'svptr' </code></pre>