Privatebin: Privatebin/YOURLS integration: API call to YOURLS not completing as intended

Created on 29 Nov 2020  ·  21Comments  ·  Source: PrivateBin/PrivateBin

This is probably not a bug I'm reporting. It's more likely a misconfiguration in my Privatebit or my YOURLS - I'm not sure which one. I have also posted about this in the YOURLS user forum at https://discourse.yourls.org/t/how-do-i-call-the-yourls-api-from-a-privatebin-instance/377

I'm using an API call to YOURLS to enable URL shortening in my Privatebin instance.
In my Privatebin’s conf.php, I have the API call to YOURLS defined as:

urlshortener = “https://t25b.xyz/yourls-api.php?signature=xxxxxxxxxx&action=shorturl&format=json&url=

Screenshot_20201129_183132

I have Privatebin at https://t25b.com and YOURLS at https://t25b.xyz. They are both hosted on the same server and the same IP address.

When Privatebin makes the API call to YOURLS, YOURL is indeed correctly registering a short URL from the Pastebin long URL, but instead of the short URL being placed in the Pastebin page in replacement of the long URL, it is being displayed in a new tab of my browser. Plus, the ‘Shorten URL’ button on the Pastebin instance is remaining enabled and I can generate another short URL (which is also displayed in a new tab).

Screenshot_20201129_190438

That is not the intended outcome, but I haven’t managed to figure out what the problem is, and whether it’s in my Pastebin configuration or my YOURLS configuration, or whether it's a bug.

The behavior can be seen now on the sites above.

If it's my configuration error, would you have any tips or solutions?
Thanks in advance if so.

bug

Most helpful comment

@ihatemyisp thank you for the feedback, I'm glad my script helped.

It would be nice if this functionality is included in next version of PrivateBin so that we don't need to manage an external script to integrate with YOURLS or bitly. This will be good for both security and maintenance.

All 21 comments

Duplicate of #238

Or not, AFAIK it should be working since https://github.com/PrivateBin/PrivateBin/issues/238 or did we miss implementing it?
I’m reopening until this is solved.

Could you in the meantime tell me which version of PB is running on that server?

It's 1.3.4. Thanks for looking at this. Pastebin's a really cool project and I want to get a really great instance of it working, for myself and for others. :-)

Do you need any other version info about the hosting server?

Same here.
My own YOURLS, PrivateBin commit from today.
In fact,it creates the shortened URL but after it displays the JSON content instead of... I 'dont know what it should displays after clicking on the "Shorten URL' button.
You can test here : https://paste.wittezaele.fr/ I don't mind about short url created... for now.

I can also confirm this behavior.

PB 1.3.4
YOURLS 1.7.10

Actually it is possible to integrate YOURLS with PrivateBin successfully and also securely by not revealing the secret "signature" field since this will allow anybody to use your YOURLS instance.

The key idea is to create a php script that will hold YOURLS "signature" secret and which will:

  • check that input URL is our PrivateBin instance URL and not some random URL
  • query our YOURLS instance using cURL through a POST request with the field "format" set to "simple"
  • output the result as an HTML page containing the shortened URL in a standard <a href=..> so that JavaScript can parse it easily.

I have been using this approach for quiet sometime in https://bin.idrix.fr (associated YOURLS instance is https://l.idrix.fr). The php code is located in the file https://bin.idrix.fr/shorten.php and in conf.php I set the following:

urlshortener = "https://bin.idrix.fr/shorten.php?link="

The content of shorten.php is below. Notice the check of the input parameter to validate that it corresponds to our PrivateBin instance and the use of cURL.
Comments are welcomed about this script.
In my opinion, trying to use YOURLS from JavaScript directly is not secure unless your PrivateBin is purely internal and not exposed to the internel. The use of an intermediary PHP script is more secure and easier to integrate with PrivateBin.

<html>
<head>
<title>IDRIX Secure Past - URL Shortening</title>

<style type="text/css">
  SPAN.shortensuccess {font-weight: bolder; color : green}
  SPAN.shortenerror {font-weight: bolder; color : red}
</style>
</head>

<body>

<?php

  $link = $_SERVER['REQUEST_URI'];
  $response = getGetData();

  $arr = explode('=',$response);
  $c = count ($arr);

  $opSuccess = FALSE;

  $shortenedUrl = "";
  $originalUrl = "";


  if(($c == 2) && ($arr[0] == "link") && (strlen($arr[1]) < 256)) {
    $decodedUrl = urldecode($arr[1]);
    if (startsWith($decodedUrl, "https://bin.idrix.fr/?")) {
        $originalUrl = $decodedUrl;

        $signature = 'XXXXXXXXXXXXXXXX';
        $api_url =  'https://l.idrix.fr/yourls-api.php';

        // Init the CURL session
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $api_url);
        curl_setopt($ch, CURLOPT_HEADER, 0);            // No header in the result
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return, do not echo result
        curl_setopt($ch, CURLOPT_POST, 1);              // This is a POST request
        curl_setopt($ch, CURLOPT_POSTFIELDS, array(     // Data to POST
                'signature' => $signature,
                'format'   => 'simple',
                'action'   => 'shorturl',
                'url' => $arr[1]
            ));

        // Fetch and return content
        $data = curl_exec($ch);
        curl_close($ch);

        if (!($data === FALSE) && is_string($data) && startsWith ($data, "https://l.idrix.fr/"))
        {
            $shortenedUrl = $data;
            $opSuccess = TRUE;
        }
    }
  }

if ($opSuccess)
{
   print("<br>Your shortened paste is <span class=\"shortensuccess\"><a href=\"$shortenedUrl\">$shortenedUrl</a></span>");
}
else
{
 print("<br><span class=\"shortenerror\">Error: An error occured while trying to shorten the given URL</span>");

}

function getGetData() {
  $data = http_build_query($_GET);
  return $data;
}

function startsWith($haystack, $needle)
{
     $length = strlen($needle);
     return (substr($haystack, 0, $length) === $needle);
}

?>

</body>
</html>

Edit by @rugk: Added language syntax highlighting.

Awesome @DalekDave maybe this is something to be documented in the wiki/FAQ?

@Mounir, thaank you for sharing your script. I am going to try it out for
myself.

@rugk, if I get this working on T25B, i will be pleased to document it in
the PB wiki.

Thanks guys! ;-)

On Sun, Dec 13, 2020 at 4:10 PM rugk notifications@github.com wrote:

Awesome @DalekDave https://github.com/DalekDave maybe this is something
to be documented in the wiki/FAQ?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/PrivateBin/PrivateBin/issues/725#issuecomment-744013424,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/APPUL5BWDYWO3XMKALJSPJLSUTDMZANCNFSM4UGUDHIA
.

I was able to get the script @idrassi posted working flawlessly.

I did have to edit the script to get it functioning in with my installation.

'url' => $originalUrl

Thanks @idrassi, this is an awesome implementation.

I'm no PHP dev, but I was able to fumble my way through adding bitly to your script @idrassi.

shortener_sec branch

I would've liked to push the settings to conf.php but I got lost trying to figure out how to do it. Feeling generous with some help @rugk?

@ihatemyisp thank you for the feedback, I'm glad my script helped.

It would be nice if this functionality is included in next version of PrivateBin so that we don't need to manage an external script to integrate with YOURLS or bitly. This will be good for both security and maintenance.

So I downloaded the shortener branch, integrated with yourls, but I get an error when trying to make a short link:
Cannot parse response from URL shortener.

Not sure if I setup CORS right, I put this in htaccess under both privatebin dir and yourls dir, they are subdirs on the same server:
Header set Access-Control-Allow-Origin "https://www.mydomain.com" Header set Access-Control-Allow-Methods "GET,PUT,POST,DELETE" Header set Access-Control-Allow-Headers "Content-Type, Authorization"
setting the origin url to my actual domain.

Also noticed in shorten.php there is a reference to url.vogon.dev - I think this should be a variable higher up?

@smpltechno Yup, I missed it during cleanup before pushing.

That might be what's causing your issue. I've fixed them now.

Ya, still gives error: Cannot parse response from URL shortener.
I have it running in subdirs:
mydomain.com/urls
mydomain.com/paste

`// [Main Settings]
// Which shortener are you using?
// Currently supported: yourls, bitly
$shortner = "yourls";

    // Your PrivateBin pastes URL template.
    // Include the /? here
    $pbUrl = "https://mydomain.com/paste/?";

    // [YOURLS Settings]
    // The URL for your YOURLS installation API.
    // This is hasn't been tested in a folder vice subdomain use case; YMMV, but in theory, I think it should work
    $yourlsUrl = "https://mydomain.com/urls/yourls-api.php";
    $yourlsBase = "https://mydomain.com/urls/"; // too lazy to code in a regex for this
    $yourlsSignature = "c5cedee3b";`

Are the URLs getting added to your YOURLS installation?

No... is my yourlsBase correct? not sure on end slash/path.

Sorry for not getting back to you sooner, and yea, it's correct, I think.

@idrassi thank you very much for your code. The URL shortening is working fine.

But after shortening the URL, I am still getting that message "URL shortener may expose your decrypt key in URL" and that warning is no longer applicable (the secret key doesn't get passed in the call to YOURLS anymare).

Do you know how I can suppress/eliminate it?
(As a quick and dirty hack, I added the following to the last line of /css/bootstrap/darkstrap-0.9.3.css :

#pastesuccess .alert.alert-danger {display: none !important;}

But that doesn't eliminate the issuing of the message as such...)

@rugk I'll be happy to document this in the wiki, or edit the existing instructions to fit, once the Privatebin project has decided whether to implement it in the codebase or else leave it as a webmaster's hack... Could you maybe update me when you're closing the ticket as fixed, or whatever? And, once again, THANK YOU GUYS FOR YOUR WORK ON PRIVATEBIN.

@DalekDave The warning is displayed when any URL shortener is used.

To get rid of it permanently, you could remove lines 525 through 528 of /tpl/bootstrap.php or surround it with a php snippet to hide them.

<?php
if(0>1):
?>                                      <div role="alert" class="alert alert-danger">
                                                <span class="glyphicon glyphicon-exclamation-sign" aria-hidden="true"></span>
                                                <?php echo I18n::_('URL shortener may expose your decrypt key in URL.'), PHP_EOL; ?>
                                        </div>
<?php
endif;
?>

Just to post an update to the above PHP code to address an issue I encountered while configuring an instance on Ubuntu 20.04 with PHP 7.4.16 and YOURLS 1.8.1. In my case, the shortened URL was redirecting to a malformed URL although the input was correctly set.

The only solution I found is to set YOURLS 'format' parameter in the cURL call to 'json' instead of 'simple' and then parse the json output to retrieve the shorted URL. This approach seems to be more robust and at the same time it is not more complex that the previous way of doing things.

Below is the new PHP code:

<?php
$link = $_SERVER['REQUEST_URI'];
$response = getGetData();

$arr = explode('=',$response);
$c = count ($arr);

$opSuccess = FALSE;

$shortenedUrl = "";
$originalUrl = "";

if(($c == 2) && ($arr[0] == "link") && (strlen($arr[1]) < 256)) {
   $decodedUrl = urldecode($arr[1]);
   if (startsWith($decodedUrl, "https://bin.veracry.pt/?")) {
      $originalUrl = $decodedUrl;

      $signature = 'xxxxxxxxxxxxx';
      $api_url =  'https://vcr.pt/yourls-api.php';

      // Init the CURL session
      $ch = curl_init();
      curl_setopt($ch, CURLOPT_URL, $api_url);
      curl_setopt($ch, CURLOPT_HEADER, 0);            // No header in the result
      curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Return, do not echo result
      curl_setopt($ch, CURLOPT_POST, 1);              // This is a POST request
      curl_setopt($ch, CURLOPT_POSTFIELDS, array(     // Data to POST
                        'signature' => $signature,
                        'format'   => 'json',
                        'action'   => 'shorturl',
                        'url' =>  $originalUrl
      ));
      // Fetch and return content
      $data = curl_exec($ch);
      curl_close($ch);
      if (!($data === FALSE) && is_string($data))
      {
          $data = json_decode( $data );
          if (!is_null($data) && array_key_exists('statusCode', $data)
                && array_key_exists('shorturl', $data) &&  ($data->statusCode == 200))
          {
              $shortenedUrl = $data->shorturl;
              $opSuccess = TRUE;
          }
      }
   }
}

if ($opSuccess)
{
   print("<br>Your shortened paste is <span class=\"shortensuccess\"><a href=\"$shortenedUrl\">$shortenedUrl</a></span>");
}
else
{
   print("<br><span class=\"shortenerror\">Error: An error occured while trying to shorten the given URL</span>");
}

function getGetData() {
   $data = http_build_query($_GET);
   return $data;
}

function startsWith($haystack, $needle)
{
   $length = strlen($needle);
   return (substr($haystack, 0, $length) === $needle);
}
?>

For LSTU API, it would also require a POST request to

Was this page helpful?
0 / 5 - 0 ratings