piątek, 18 października 2013

Rozszyfrowywanie CAPTCHA z pomocą zewnętrznego (płatnego) klienta

CAPTCHA, to rodzaj techniki stosowanej jako zabezpieczenie na stronach www, celem której jest dopuszczenie do przesłania danych tylko wypełnionych przez człowieka link do wikipedii.

Najczęściej jest to rysunek, który wymaga od użytkownika własnoręcznego wpisania, tworzony w taki sposób, aby rozwiązanie tego sposobem mechanicznym było maksymalnie skomplikowane. Same algorytmy rozwiązywania captcha są skomplikowane i bazują na algorytmach szyfrowania i deszyfrowania. Na szczęście, nie musimy się znać na wszystkim i można te zadanie zlecić komuś innemu, kto się na tym zna dużo lepiej niż my. Przykładem firmy, która się specjalizuje w rozwiązywania zabezpieczeń typu CAPTCHA jest deathbycaptcha.

Najnowsza wersja (4.2) posiada bibliotekę przygotowaną dla języków:
-.NET (C#, Visual Basic)
- AutoIt3
- iMacros
- C (client and libs' source code)
- Java
- Perl 5+
- PHP v5+
- Python v2.5+ and v3.0+
- Command-line tool for Windows, Linux (i386 and x86-64) (see usage note below)
- Plugin for MyAdTools bots

oraz klienta firm 3 (nie wspierane przez producenta)
- Ruby

Producent udostępnia przykład wykorzystania API w językach:
- PHP
- C#

Osobiście wybrałem przykład działający w C# i działa tak, jak należy, tzn. do projektu dodajemy bibliotekę .dll, w wykonywanym pliku .cs dokładamy nową przestrzeń nazw, za pomocą której tworzymy obiekt. Na obiekcie wywołujemy przeciążoną metodę .Decode (captcha możemy podać, np. jako tablicę bajtów) i... to wszystko :)
Dodatkowo mamy metodę GetBalance(), która sprawdza bilans naszego konta (czyli w razie potrzeby możemy napisać automat przypominający nam o tym, że kończą się pieniądze przeznaczone na rozwiązywanie captcha).


Ile to kosztuje?
1k Captcha - 1.390 dolara, co przy aktualnym kursie dolara daje nam ~ 4.22zł za każde rozwiązane 1k CAPTCHA, czyli nie jest to jakiś nie wiadomo jaki majątek.

poniedziałek, 14 października 2013

.NET Client consuming PHP ZendFramework Web Service - solution no.2 (HttpWebRequest)

Dostałem za zadanie zintegrowanie się z kilkoma web serwisami. Naturalnym rozwiązaniem było wybranie do tego celu technologii WPF, co opisałem we wcześniejszym wpisie. Na szczęście, platforma .NET pozwala wykonywać wiele zdań na wiele, różnorodnych sposobów. W tym wpisie, przedstawię inny sposób odpytania web serwisu, tj. za pomocą klasy HttpWebRequest

W przypadku korzystania z klasy HttpWebRequest, całość zapytania SOAP, jakie wykonamy musimy napisać samemu w stringu, a nast. odpytać serwis za pomocą metody POST.
Odpowiedź dostajemy w strumieniu bajtów, który następnie zamieniamy na XmlDocument

        public string GetMigDZiDByPost()
        {
            try
            {
                WebRequest webRequest = WebRequest.Create("http://xxx.yyy.zzz/Dzsoapserver/queryko");

                HttpWebRequest httpRequest = (HttpWebRequest)webRequest;
                httpRequest.Method = "POST";
                httpRequest.ContentType = "text/xml";
                httpRequest.Headers.Add("SOAPAction: http://xxx.yyy.zzz/Dzsoapserver/queryko");
                Stream requestStream = httpRequest.GetRequestStream();

                //Create Stream and Complete Request
                StreamWriter streamWriter = new StreamWriter(requestStream);
                streamWriter.Write(this.GetSoapString());
                streamWriter.Close();

                //Get the Response
                WebResponse webResponse = httpRequest.GetResponse();
                Stream responseStream = webResponse.GetResponseStream();
                StreamReader streamReader = new StreamReader(responseStream);

                //Read the response into an xml document
                System.Xml.XmlDocument soapResonseXMLDocument = new System.Xml.XmlDocument();
                soapResonseXMLDocument.LoadXml(streamReader.ReadToEnd());

                //return only the xml representing the response details (inner request)
                string test = soapResonseXMLDocument.GetElementsByTagName("organisation")[0].InnerXml;
                return test;
            }
            catch (Exception ex)
            {
                //error 500 mean rekord not found
                return null;
            }
        }
        private string GetSoapString()
        {
            string hash = Constraints.MIGDZ.MigDzHashBase64;
            StringBuilder sb = new StringBuilder();
            sb.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            sb.Append("<SOAP-ENV:Envelope SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:ns1=\"http://xxx.yyy.zzz/Dzsoapserver/queryko\" xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Body>");
            sb.Append("<ns1:getParcel><parameters xsi:type=\"ns1:Logic_DzSoapServer_Request_BodyKo\"><sy xsi:type=\"xsd:string\">12345678</sy></parameters><hashCode xsi:type=\"xsd:string\">abcdefg0123456VUZ123ABCD01a=</hashCode></ns1:getParcel></SOAP-ENV:Body></SOAP-ENV:Envelope>");

            return sb.ToString();
        }

piątek, 11 października 2013

.NET Client consuming PHP ZendFramework Web Service - solution no.1 (WPF)

 Dostałem za zadanie zintegrowanie się z kilkoma web servisami. Naturalnym rozwiązaniem było wybranie do tego celu technologii WPF, szczególnie, że korzystanie z jego poprzednika zwanego potocznie "(SOAP) Web Services" jest niezalecane przez firmę Microsoft od 2009r:
 W książce "Professional-ASP-NET-4-5-in-C-and-VB.productCd-1118311825.html" jest napisane:

In 2009, ASMX Web Services were marked as legacy. Therefore, the code found within will notbe updated with future ASP.NET releases (the exception would be a security update).

 O ile zintegrowanie się z serwisami wystawionymi przez inne WCF poszło gładko, to pewne problemy napotkałem podczas integracji z web serwisem napisanym w PHP przy pomocy Zend Framework. Początkowo chciałem wykonać standardową operację połączenia, która powinna wyglądać tak samo, jak w przypadku korzystania z serwisu napisanego w WCF (link do artykułu, w którym programista łączy się z php za pomocą wcf). Niestety, w moim przypadku, WCF wykrył usługę, ale wyrzucił błąd:

There was an error downloading 'http://xxx.yyy.zzz.pl/Swdsoapserver/query?wsdl/_vti_bin/ListData.svc/$metadata'.
The request failed with the error message:
--
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"><SOAP-ENV:Body><SOAP-ENV:Fault><faultcode>Sender</faultcode><faultstring>Invalid XML</faultstring></SOAP-ENV:Fault></SOAP-ENV:Body></SOAP-ENV:Envelope>

--.
Metadata contains a reference that cannot be resolved: 'http://xxx.yyy.zzz.pl/Swdsoapserver/query?wsdl'.
The content type text/html; charset=UTF-8 of the response message does not match the content type of the binding (application/soap+xml; charset=utf-8). If using a custom encoder, be sure that the IsContentTypeSupported method is implemented properly. The first 1024 bytes of the response were: '<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:tns="http://xxx.yyy.zzz.pl/Swdsoapserver/query" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap-enc="http://schemas.xmlsoap.org/soap/encoding/"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" name="SwdQuerySoap"
    targetNamespace="http://xxx.yyy.zzz.pl/Swdsoapserver/query">
    <wsdl:types>
        <xsd:schema 
 Jak zwykle w takich przypadkach nieodzowne okazało się forum stackoverflow, gdzie zadałem pytanie: "consuming-php-web-service-by-wcf-client".
 Dzięki otrzymanej tam pomocy, zapisałem plik .wsdl na dysku i za jego pomocą utworzyłem klasy proxy (Add Service Reference, tylko zamiast linku, wskazujemy lokalizację pliku na dysku).

Tak wygenerowane klasy proxy potrafiły się połączyć i wykonać zapytanie, ale miały problemy z odczytaniem odpowiedzi. Błędy były dwa:

a) Wielkość przesyłanej odpowiedzi była większa, niż długość standardowej odpowiedzi
"Maksymalny przydział rozmiaru wiadomości dla wiadomości przychodzących (65536) został przekroczony. Aby zwiększyć przydział, użyj właściwości MaxReceivedMessageSize we właściwym elemencie wiązania."
Obejście tego problemu jest stosunkowo proste i polega na modyfikacji ustawień powiązania (ang. binding) wewnątrz pliku web.config/app.config naszej aplikacji:

        <binding name="Logic_DzSoapServer_QueryKoBinding" maxBufferSize="2147483647"
          maxReceivedMessageSize="2147483647">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647"
            maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
        </binding>
b) Problem mappera z parsowaniem odpowiedzi wysyłanej przez PHP na obiekty .NET
"Nie można przypisać obiektu typu System.Int32 do obiektu typu System.Int32[]."
Sposobem aby to obejść okazała się ręczna modyfikacja wygenerowanych klas proxy, aby zamiast spodziewanego typu odpowiedzi, odpowiadały typem 'object':

        public object getParcel(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode) {
            return base.Channel.getParcel(parameters, hashCode);
        }

        public object getParcelAsync(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode)
        {
            return base.Channel.getParcelAsync(parameters, hashCode);
        }

     [System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "4.0.0.0")]
    [System.ServiceModel.ServiceContractAttribute(Namespace="http://xxx.yyy.zzz.pl/Dzsoapserver/queryko", ConfigurationName="ko.Logic_DzSoapServer_QueryKoPort")]
    public interface Logic_DzSoapServer_QueryKoPort {
      
        [System.ServiceModel.OperationContractAttribute(Action="http://xxx.yyy.zzz.pl/Dzsoapserver/queryko#getParcel", ReplyAction="*")]
    [System.ServiceModel.XmlSerializerFormatAttribute(Style=System.ServiceModel.OperationFormatStyle.Rpc, SupportFaults=true, Use=System.ServiceModel.OperationFormatUse.Encoded)]
        [return: System.ServiceModel.MessageParameterAttribute(Name="return")]
        object getParcel(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode);
      
        [System.ServiceModel.OperationContractAttribute(Action="http://xxx.yyy.zzz.pl/Dzsoapserver/queryko#getParcel", ReplyAction="*")]
        [return: System.ServiceModel.MessageParameterAttribute(Name="return")]
        object getParcelAsync(CreditRiskEngine.ko.Logic_DzSoapServer_Request_BodyKo parameters, string hashCode);
    }

 Co spowodowało, że wywołanie odpowiedzi było stosunkowo proste:

            List<MigDzKo> migDzKoList = new List<MigDzKo>();
            try
            {
                ko.Logic_DzSoapServer_QueryKoPortClient client = new ko.Logic_DzSoapServer_QueryKoPortClient();
                ko.Logic_DzSoapServer_Request_BodyKo request = new ko.Logic_DzSoapServer_Request_BodyKo();
                request.sy = "12345678";
                client.Open();
                var response = client.getParcel(request, Constraints.MIGDZ.MigDzHashBase64);
                XmlNode[] xmlNodes = (XmlNode[])response;
                for (int i = 0; i < xmlNodes.Length; i++)
                {
                    try
                    {
                        string organization = string.Empty;
                        if (xmlNodes[i].ChildNodes[0].HasChildNodes)
                        {
                            MigDzKo migDzKo = new MigDzKo();
                            migDzKo.operation_id = Convert.ToInt32(xmlNodes[i].ChildNodes[0]["operation_id"].InnerXml);
                            migDzKo.hashpublic = xmlNodes[i].ChildNodes[0]["hashpublic"].InnerXml;
                            migDzKoList.Add(migDzKo);
                        }
                    }
                    catch (Exception ex)
                    {
                        log.Error(ex.Message.ToString());
                    }
                }
                client.Close();
            }
            catch (Exception ex)
            {
                log.Error(ex.Message.ToString());
            }
            return migDzKoList;


Dla spójności podaję też zawartość klasy MigDzKo

    public class MigDzKo
    {
        public int operation_id { get; set; }
        public string hashpublic { get; set; }
    }

poniedziałek, 30 września 2013

Odrobina kryptografi

W przypadku, gdy zaistnieje potrzeba wykorzystania kryptografii, tj. zakodowania pewnych danych w aplikacji (z możliwością ich późniejszego odszyfrowania), powinno się unikać korzystania ze standardowych algorytmów szyfrujących dostępnych w .NET 2.0, ponieważ nie zapewniają one wystarczającego bezpieczeństwa.  W miarę bezpieczny, wydaje się algorytm  AES, jednak jak to w kryptografii bywa, skuteczna użyteczność poszczególnych algorytmów z biegiem czasu maleje.

Link, do działającego algorytmu w C#, można znaleźć na stackoverflow.com

Osobiście miałem okazję z niego korzystać w momencie, gdy jedynym skutecznym sposobem przekazania danych identyfikacyjnych z głównego okna aplikacji do popupa było wysłanie danych za pomocą querystringa (klient zażyczył sobie przeniesienie wszystkich plików typu BLOB z naszej bazy danych, do zewnętrznego syst. plików obsługiwanego przez firmę zewnętrzną z możliwością obsługi załączników do wniosku przez aplikacje trzecie).

Inne przydatne linki:
stackoverflow.com - how-to-start-learn-cryptography-with-c-sharp
http://stackoverflow.com - c-sharp-rsa-encryption-decryption-with-transmission



czwartek, 26 września 2013

Datatable to CSV (Excel)

Ost. zostałem poproszony o pomoc w pewnej sprawie. Sprawa dotyczyła... problemu z zapisem, a nast. odczytaniem dokumentu CSV na dysku. No cóż. Nie pozostało mi nic innego, jak zamienić FileStream na MemoryStream, a nast. przekazać go do obiektu klasy Attachment (pomijając zbędne zapisywanie załącznika na dysku). Przy okazji poprawiłem też metodę tworzenia samego załącznika (zapytanie SQL zwracało DataTable, który nast. był konwertowany na CSV). Całkiem sprytne i zgrabne rozwiązanie znalazłem na stackoverflow.com (convert-datatable-to-csv-stream).

Dlaczego więc piszę tutaj o tym? Ponieważ, w rozwiązaniu, które jest na stackoverflow, dokonałem kilku kosmetycznych zmian i postanowiłem je sobie tutaj zachować na przyszłość.

W klasie wywołującej kodowanie: "Windows-1250"
W klasie Extensions, w metodzie ToCSV znak oddzielający: ";"

środa, 25 września 2013

Konfiguracja IIS do hostowania aplikacji wykorzystującej SSL

Zgodnie z zapowiedzią, zaprezentowaną jakiś czas temu tutaj na blogu, po blisko 2 latach postanowiłem porzucić sharepointa i skoncentrować się na programowaniu w .NET. Jednym z zadań, jakie zostało mi postawionych, było stworzenie aplikacji webowej\web service, który jako swój element składowy będzie wykorzystywał inny web service zabezpieczony certyfikatem SSL.

Przystąpiłem więc do pracy. Pierwszą rzeczą, którą zrobiłem, to zainstalowałem certyfikat na komputerze (dwuklik na certyfikacie, a nast. 'next->next->wpisz hasło -> next->itd.').

Następnie stworzyłem aplikację webową, która wykorzystywała ten web service i wyświetlała odpowiednie dane na stronie .aspx. Pocz. visual studio nie chciało się u mnie podłączyć do web service (zapewne kwestia servera proxy w korpo), bo z 'iPlusa' wszystko chodziło tak jak należy.
Sposobem, aby zaczęło mi to działać w sieci korporacyjnej, okazało się włączenie programu Fiddler. Początkowo planowałem go użyć do zdiagnozowania problemu, natomiast... samo jego wykorzystanie rozwiązało mój problem na maszynie developerskiej (zapewne wynika to z faktu, że Fiddler w domyślnych ustawieniach wymusza autentykację).


Ok, aplikacja na maszynie developerskiej to jedno. Jeszcze trzeba to postawić na IIS, aby inni ludzie mieli do tego dostęp. O ile stworzenie puli aplikacji i wgranie tam strony było proste, o tyle sprawienie, aby nasza aplikacja mogą bez problemów wykorzystywać zewnętrzny web service z SSL przysporzyło mi nieco pracy (Error 403).


Rozwiązanie polega na tym, że należy stworzyć własny certyfikat i ustawić w ISS 'powiązania' (ang. 'bindings'). Jak to zrobić można przeczytać tutaj oraz tutaj (2 art. do czytania)


oraz upewnić się w ustawieniach puli aplikacji, że aplikacja będzie uruchamiana z konkretną tożsamością użytkownika (ponieważ ta konkretna aplikacja nie posiadała swojego typu 'logowania', to jest personifikowana jako konto admina w puli aplikacji).



wtorek, 30 lipca 2013

Entity Framework Modified State (updating row values)

Pogłębiając moją wiedzę nt. Entity Framework zauważyłem pewną ciekawą właściwość, mianowicie w syt. gdy tylko niektóre kolumny w wierszu z danymi zostały zaktualizowane, to Entity Framework zachowuje się w różny sposób dla różnego rodzaju aplikacji:
- dla aplikacji okienkowych (windows forms) aktualizuje tylko wybrane kolumny danego rekordu
- dla aplikacji webowych (asp) aktualizuje wszystkie kolumny danego rekordu


In a desktop application, state changes are typically set automatically. In this type of application, you read an entity and make changes to some of its property values. This causes its entity state to automatically be changed to Modified. Then when you call SaveChanges, the Entity Framework generates a SQL UPDATE statement that updates only the actual properties that you changed.
However, in a web application this sequence is interrupted, because the database context instance that reads an entity is disposed after a page is rendered. When the HttpPost Edit action method is called, this is the result of a new request and you have a new instance of the context, so you have to manually set the entity state to Modified. Then when you call SaveChanges, the Entity Framework updates all columns of the database row, because the context has no way to know which properties you changed.
If you want the SQL Update statement to update only the fields that the user actually changed, you can save the original values in some way (such as hidden fields) so that they are available when the HttpPost Edit method is called. Then you can create a Student entity using the original values, call the Attach method with that original version of the entity, update the entity's values to the new values, and then call SaveChanges. For more information, see Add/Attach and Entity States and Local Data on the Entity Framework team blog.
 źródło